mirror of
https://github.com/pi-hole/web.git
synced 2024-12-06 19:36:21 +01:00
Merge pull request #197 from DL6ER/auth
Add server-side password protection for the web interface
This commit is contained in:
@@ -1,13 +1,18 @@
|
||||
<?php
|
||||
$api = true;
|
||||
require "php/password.php";
|
||||
|
||||
include('data.php');
|
||||
header('Content-type: application/json');
|
||||
|
||||
$data = array();
|
||||
|
||||
// Works without authorization
|
||||
if (isset($_GET['summaryRaw'])) {
|
||||
$data = array_merge($data, getSummaryData());
|
||||
}
|
||||
|
||||
// Works without authorization
|
||||
if (isset($_GET['summary']) || !count($_GET)) {
|
||||
$sum = getSummaryData();
|
||||
$sum['ads_blocked_today'] = number_format( $sum['ads_blocked_today']);
|
||||
@@ -17,37 +22,45 @@
|
||||
$data = array_merge($data, $sum);
|
||||
}
|
||||
|
||||
// Works without authorization
|
||||
if (isset($_GET['overTimeData'])) {
|
||||
$data = array_merge($data, getOverTimeData());
|
||||
}
|
||||
|
||||
// Works without authorization
|
||||
if (isset($_GET['overTimeData10mins'])) {
|
||||
$data = array_merge($data, getOverTimeData10mins());
|
||||
}
|
||||
|
||||
if (isset($_GET['topItems'])) {
|
||||
// Requires authorization
|
||||
if (isset($_GET['topItems']) && $auth) {
|
||||
$data = array_merge($data, getTopItems());
|
||||
}
|
||||
|
||||
if (isset($_GET['recentItems'])) {
|
||||
// Requires authorization
|
||||
if (isset($_GET['recentItems']) && $auth) {
|
||||
if (is_numeric($_GET['recentItems'])) {
|
||||
$data = array_merge($data, getRecentItems($_GET['recentItems']));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['getQueryTypes'])) {
|
||||
// Requires authorization
|
||||
if (isset($_GET['getQueryTypes']) && $auth) {
|
||||
$data = array_merge($data, getIpvType());
|
||||
}
|
||||
|
||||
if (isset($_GET['getForwardDestinations'])) {
|
||||
// Requires authorization
|
||||
if (isset($_GET['getForwardDestinations']) && $auth) {
|
||||
$data = array_merge($data, getForwardDestinations());
|
||||
}
|
||||
|
||||
if (isset($_GET['getQuerySources'])) {
|
||||
// Requires authorization
|
||||
if (isset($_GET['getQuerySources']) && $auth) {
|
||||
$data = array_merge($data, getQuerySources());
|
||||
}
|
||||
|
||||
if (isset($_GET['getAllQueries'])) {
|
||||
// Requires authorization
|
||||
if (isset($_GET['getAllQueries']) && $auth) {
|
||||
$data = array_merge($data, getAllQueries());
|
||||
}
|
||||
|
||||
|
||||
+54
-2
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
if (isset($_GET['enable'])) {
|
||||
require "php/password.php";
|
||||
|
||||
if (isset($_GET['enable']) && $auth) {
|
||||
exec('sudo pihole enable');
|
||||
$refer = $_SERVER['HTTP_REFERER'];
|
||||
header("location:$refer");
|
||||
} elseif (isset($_GET['disable'])) {
|
||||
} elseif (isset($_GET['disable']) && $auth) {
|
||||
exec('sudo pihole disable');
|
||||
$refer = $_SERVER['HTTP_REFERER'];
|
||||
header("location:$refer");
|
||||
@@ -44,6 +46,9 @@
|
||||
$used = $mem[2] - $mem[5] - $mem[6];
|
||||
$total = $mem[1];
|
||||
$memory_usage = $used/$total*100;
|
||||
|
||||
// For session timer
|
||||
$maxlifetime = ini_get("session.gc_maxlifetime");
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
@@ -134,6 +139,7 @@
|
||||
<div class="col-xs-4 text-center">
|
||||
<a href="https://github.com/pi-hole/pi-hole/releases">Updates</a>
|
||||
</div>
|
||||
<div class="col-xs-12 text-center" id="sessiontimer">Session is valid for <span id="sessiontimercounter"><?php if($auth && strlen($pwhash) > 0){echo $maxlifetime;}else{echo "0";} ?></span></div>
|
||||
</li>
|
||||
<!-- Menu Footer -->
|
||||
<li class="user-footer">
|
||||
@@ -232,6 +238,7 @@
|
||||
<i class="fa fa-home"></i> <span>Main Page</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php if($auth){ ?>
|
||||
<!-- Query Log -->
|
||||
<li>
|
||||
<a href="queries.php">
|
||||
@@ -270,18 +277,41 @@
|
||||
echo ' <li><a href="?enable"><i class="fa fa-play"></i> <span>Enable</span></a></li>';
|
||||
}
|
||||
?>
|
||||
<!-- Logout -->
|
||||
<?php
|
||||
// Show Logout button if $auth is set and authorization is required
|
||||
if(strlen($pwhash) > 0) { ?>
|
||||
<li>
|
||||
<a href="index.php?logout">
|
||||
<i class="fa fa-user-times"></i> <span>Logout</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<!-- Login -->
|
||||
<?php
|
||||
// Show Login button if $auth is *not* set and authorization is required
|
||||
if(strlen($pwhash) > 0 && !$auth) { ?>
|
||||
<li>
|
||||
<a href="index.php?login">
|
||||
<i class="fa fa-user"></i> <span>Login</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<!-- Donate -->
|
||||
<li>
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY">
|
||||
<i class="fa fa-paypal"></i> <span>Donate</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php if($auth){ ?>
|
||||
<!-- Help -->
|
||||
<li>
|
||||
<a href="help.php">
|
||||
<i class="fa fa-question-circle"></i> <span>Help</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</section>
|
||||
<!-- /.sidebar -->
|
||||
@@ -290,3 +320,25 @@
|
||||
<div class="content-wrapper">
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<?php
|
||||
// If password is not equal to the password set
|
||||
// in the setupVars.conf file, then we skip any
|
||||
// content and just complete the page. If no
|
||||
// password is set at all, we keep the current
|
||||
// behavior: everything is always authorized
|
||||
// and will be displayed
|
||||
//
|
||||
// If auth is required and not set, i.e. no successfully logged in,
|
||||
// we show the reduced version of the summary (index) page
|
||||
if(!$auth && (!isset($indexpage) || isset($_GET['login']))){ ?>
|
||||
<div class="page-header">
|
||||
<h1>Login required!</h1>
|
||||
</div>
|
||||
<form action="" method="POST">
|
||||
Password: <input type="password" name="pw"> <input type="submit" value="Login">
|
||||
</form>
|
||||
<?php
|
||||
require "footer.php";
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
$indexpage = true;
|
||||
require "header.php";
|
||||
?>
|
||||
<!-- Small boxes (Stat box) -->
|
||||
@@ -75,7 +76,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// If the user is logged in, then we show the more detailed index page.
|
||||
// Even if we would include them here anyhow, there would be nothing to
|
||||
// show since the API will respect the privacy of the user if he defines
|
||||
// a password
|
||||
if($auth){ ?>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="box" id="query-types">
|
||||
@@ -195,7 +201,7 @@
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
<?php } ?>
|
||||
<?php
|
||||
require "footer.php";
|
||||
?>
|
||||
|
||||
@@ -55,3 +55,42 @@ $.getJSON("https://api.github.com/repos/pi-hole/AdminLTE/releases/latest", funct
|
||||
*/
|
||||
if(piholeVersion !== "vDev" && versionCompare(piholeVersion, "v2.7") < 0)
|
||||
alert("Pi-hole needs to be updated to at least v2.7 before you can use features such as whitelisting/blacklisting from this web interface!")
|
||||
|
||||
// Session timer
|
||||
var sessionvalidity = parseInt(document.getElementById("sessiontimercounter").textContent);
|
||||
var start = new Date;
|
||||
|
||||
function updateSessionTimer()
|
||||
{
|
||||
start = new Date;
|
||||
start.setSeconds(start.getSeconds() + sessionvalidity);
|
||||
}
|
||||
|
||||
if(sessionvalidity > 0)
|
||||
{
|
||||
// setSeconds will correctly handle wrap-around cases
|
||||
updateSessionTimer();
|
||||
|
||||
setInterval(function() {
|
||||
var current = new Date;
|
||||
var totalseconds = (start - current) / 1000;
|
||||
|
||||
// var hours = Math.floor(totalseconds / 3600);
|
||||
// totalseconds = totalseconds % 3600;
|
||||
|
||||
var minutes = Math.floor(totalseconds / 60);
|
||||
if(minutes < 10){ minutes = "0" + minutes; }
|
||||
|
||||
var seconds = Math.floor(totalseconds % 60);
|
||||
if(seconds < 10){ seconds = "0" + seconds; }
|
||||
|
||||
if(totalseconds > 0)
|
||||
document.getElementById("sessiontimercounter").textContent = minutes + ":" + seconds;
|
||||
else
|
||||
document.getElementById("sessiontimercounter").textContent = "-- : --";
|
||||
}, 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("sessiontimer").style.display = "none";
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
// Remove JS warning
|
||||
var jswarn = document.getElementById("js-warn-exit");
|
||||
jswarn.parentNode.removeChild(jswarn);
|
||||
jswarn.parentNode.removeChild(jswarn);
|
||||
|
||||
+58
-41
@@ -24,7 +24,7 @@ $(document).ready(function() {
|
||||
return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Windows());
|
||||
}
|
||||
};
|
||||
var animate = false;
|
||||
|
||||
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
|
||||
timeLineChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
@@ -96,55 +96,70 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
ctx = document.getElementById("queryTypeChart").getContext("2d");
|
||||
queryTypeChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
animation: {
|
||||
duration: 2000
|
||||
},
|
||||
cutoutPercentage: 0
|
||||
}
|
||||
});
|
||||
|
||||
ctx = document.getElementById("forwardDestinationChart").getContext("2d");
|
||||
forwardDestinationChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
animation: {
|
||||
duration: 2000
|
||||
},
|
||||
cutoutPercentage: 0
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
|
||||
updateSummaryData();
|
||||
|
||||
updateQueriesOverTime();
|
||||
|
||||
updateQueryTypes();
|
||||
// Create / load "Query Types" only if authorized
|
||||
if(!!document.getElementById("queryTypeChart"))
|
||||
{
|
||||
ctx = document.getElementById("queryTypeChart").getContext("2d");
|
||||
queryTypeChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
animation: {
|
||||
duration: 2000
|
||||
},
|
||||
cutoutPercentage: 0
|
||||
}
|
||||
});
|
||||
updateQueryTypes();
|
||||
}
|
||||
|
||||
updateTopClientsChart();
|
||||
// Create / load "Forward Destinations" only if authorized
|
||||
if(!!document.getElementById("forwardDestinationChart"))
|
||||
{
|
||||
ctx = document.getElementById("forwardDestinationChart").getContext("2d");
|
||||
forwardDestinationChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
animation: {
|
||||
duration: 2000
|
||||
},
|
||||
cutoutPercentage: 0
|
||||
}
|
||||
});
|
||||
updateForwardDestinations();
|
||||
}
|
||||
|
||||
updateForwardDestinations();
|
||||
// Create / load "Top Domains" and "Top Advertisers" only if authorized
|
||||
if(!!document.getElementById("domain-frequency")
|
||||
&& !!document.getElementById("ad-frequency"))
|
||||
{
|
||||
updateTopLists();
|
||||
}
|
||||
|
||||
updateTopLists();
|
||||
// Create / load "Top Clients" only if authorized
|
||||
if(!!document.getElementById("client-frequency"))
|
||||
{
|
||||
updateTopClientsChart();
|
||||
}
|
||||
});
|
||||
|
||||
// Functions to update data in page
|
||||
@@ -169,6 +184,8 @@ function updateSummaryData(runOnce) {
|
||||
$("h3#ads_percentage_today").text(data.ads_percentage_today + "%");
|
||||
$("h3.statistic.glow").removeClass("glow")
|
||||
}, 500);
|
||||
|
||||
updateSessionTimer();
|
||||
}).done(function() {
|
||||
if (runOnce !== true) {
|
||||
setTimeout(updateSummaryData, 10000);
|
||||
|
||||
@@ -72,6 +72,7 @@ $(document).ready(function() {
|
||||
|
||||
function refreshData() {
|
||||
tableApi.ajax.url("api.php?getAllQueries").load();
|
||||
// updateSessionTimer();
|
||||
}
|
||||
|
||||
function add(domain,list) {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
require "password.php";
|
||||
if(!$auth) die("Not authorized");
|
||||
|
||||
ob_end_flush();
|
||||
ini_set("output_buffering", "0");
|
||||
ob_implicit_flush(true);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// Start a new PHP session (or continue an existing one)
|
||||
session_start();
|
||||
$pwhash = parse_ini_file("/etc/pihole/setupVars.conf")['WEBPASSWORD'];
|
||||
|
||||
// If the user wants to log out, we free all session variables currently registered
|
||||
if(isset($_GET["logout"]))
|
||||
{
|
||||
session_unset();
|
||||
}
|
||||
|
||||
// Test if password is set
|
||||
if(strlen($pwhash) > 0)
|
||||
{
|
||||
// Compare doubly hashes password input with saved hash
|
||||
if(isset($_POST["pw"]))
|
||||
{
|
||||
$postinput = hash('sha256',hash('sha256',$_POST["pw"]));
|
||||
if($postinput == $pwhash)
|
||||
{
|
||||
$_SESSION["hash"] = $pwhash;
|
||||
$auth = true;
|
||||
}
|
||||
}
|
||||
// Compare auth hash with saved hash
|
||||
else if (isset($_SESSION["hash"]))
|
||||
{
|
||||
if($_SESSION["hash"] == $pwhash)
|
||||
$auth = true;
|
||||
}
|
||||
// API can use the hash to get data without logging in via plain-text password
|
||||
else if (isset($api) && isset($_GET["auth"]))
|
||||
{
|
||||
if($_GET["auth"] == $pwhash)
|
||||
$auth = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Password or hash wrong
|
||||
$auth = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No password set
|
||||
$auth = true;
|
||||
}
|
||||
?>
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
require "password.php";
|
||||
if(!$auth) die("Not authorized");
|
||||
|
||||
ob_end_flush();
|
||||
ini_set("output_buffering", "0");
|
||||
ob_implicit_flush(true);
|
||||
|
||||
Reference in New Issue
Block a user