Merge pull request #95 from george-e-shaw-iv/master

Implimented IP filtering addition in account panel
This commit is contained in:
George Shaw 2017-11-30 16:25:10 -06:00 committed by GitHub
commit 40e1e3d9d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 338 additions and 49 deletions

View file

@ -0,0 +1,22 @@
var ipModal = jQuery('.ip-filter-modal');
jQuery('._js_ip-filter-form').on('submit', function(e){
e.preventDefault();
var requestData = {};
requestData["ip"] = jQuery(this).find('input[name="ip"]').val();
requestData["type"] = jQuery(this).find('input[name="type"]').val();
var xhr = new XMLHttpRequest();
xhr.open(jQuery(this).attr('method'), jQuery(this).attr('action'), true);
xhr.send(JSON.stringify(requestData));
xhr.onloadend = function() {
if (xhr.status == 204) {
listFilteredIPs(requestData["type"]);
}
else {
alert("Something went wrong trying to filter that IP, please contact your administrator if problem persists.");
}
}
});

View file

@ -1,22 +0,0 @@
var ipModal = jQuery('.ip-filter-modal');
jQuery('._js_ip-filtering-open').on('click', function(e){
e.preventDefault();
var title;
switch(jQuery(this).attr('data')) {
case "general":
title = "General";
break;
case "maintenance":
title = "Maintenance Mode";
break;
default:
alert("Error, refresh and try again. If problem persists contact server administrator.");
return;
}
title += " IP Filtering";
ipModal.find('.modal-title').html(title);
ipModal.modal('show');
});

View file

@ -0,0 +1,66 @@
var ipModal = jQuery('.ip-filter-modal');
jQuery('._js_ip-filtering-open').on('click', function(e){
e.preventDefault();
var title;
switch(jQuery(this).attr('data')) {
case "block":
title = "Block";
ipModal.find('input[name="type"]').attr('value', 'block');
ipModal.find('#filterIPHelp').html("Filtering this IP under the block filter type will disallow access to the website for all modes.");
break;
case "maintenance":
title = "Maintenance";
ipModal.find('input[name="type"]').attr('value', 'maintenance');
ipModal.find('#filterIPHelp').html("Whitelisting this IP under the maintenance filter type will allow access to the website during maintenance mode.");
break;
default:
alert("Error, refresh and try again. If problem persists contact server administrator.");
return;
}
title = "IP Filtering - " + title;
ipModal.find('.modal-title').html(title);
listFilteredIPs(jQuery(this).attr('data'));
ipModal.modal('show');
});
function listFilteredIPs(type) {
ipModal.find('._js_currently-filtered-ips').html('');
var requestData = {}
requestData["type"] = type;
var xhr = new XMLHttpRequest();
xhr.open('POST', 'api/ip/list', true);
xhr.send(JSON.stringify(requestData));
xhr.onloadend = function() {
if(xhr.status == 200) {
if(xhr.response != undefined && xhr.response.length != 0) {
jsonResponse = JSON.parse(xhr.response)
console.log(xhr.response);
jQuery.each(jsonResponse, function(k, v) {
ipModal.find('._js_currently-filtered-ips').append("<li>"+v.ip+"</li>");
});
}
else {
ipModal.find('.modal-body').html("An error has occurred, please refresh. If problem persists contact your administrator.");
}
}
else if(xhr.status == 204) {
ipModal.find('._js_currently-filtered-ips').append("<li>No Filtered IPs Currently Exist.</li>");
}
else {
if(xhr.response != undefined && xhr.response.length != 0) {
ipModal.find('.modal-body').html(xhr.response);
}
else {
ipModal.find('.modal-body').html("An error has occurred, please refresh. If problem persists contact your administrator.");
}
}
}
}

View file

@ -59,7 +59,18 @@
</button>
</div>
<div class="modal-body">
<p>Coming soon!</p>
<form class="_js_ip-filter-form" action="api/ip/filter" method="POST">
<div class="form-group">
<label for="ip">Filter IP</label>
<input name="ip" type="text" class="form-control" id="ip" aria-describedby="filterIPHelp" placeholder="127.0.0.1">
<small id="filterIPHelp" class="form-text text-muted"></small>
</div>
<input type="hidden" name="type" value="">
<button type="submit" class="btn btn-primary">Filter IP</button>
</form>
<h4 class="mt-3">Currently Filtered IPs</h4>
<ul class="_js_currently-filtered-ips"></ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
@ -133,8 +144,8 @@
IP Filtering
</button>
<div class="dropdown-menu" aria-labelledby="ipFilterDropdown">
<a class="dropdown-item _js_ip-filtering-open" href="#general_ip_filter" data="general">General</a>
<a class="dropdown-item _js_ip-filtering-open" href="#maintenance_ip_filter" data="maintenance">Maintenance Mode</a>
<a class="dropdown-item _js_ip-filtering-open" href="#block_ip_filter" data="block">Block</a>
<a class="dropdown-item _js_ip-filtering-open" href="#maintenance_ip_filter" data="maintenance">Maintenance</a>
</div>
</div>
</div>
@ -174,7 +185,8 @@
<script type="text/javascript" src="assets/js/panelHandlers/log/view.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/log/delete.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/security/ipFiltering.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/security/ip_list.js"></script>
<script type="text/javascript" src="assets/js/panelHandlers/security/filter_ip.js"></script>
<!-- KEEP AT BOTTOM OF BODY TAGS -->
</body>
</html>

View file

@ -17,7 +17,6 @@ func List(res http.ResponseWriter, req *http.Request, logger *log.Logger, bundle
}
if len(bundles) <= 0 {
logger.Println("no bundles :: http response returns no content")
res.WriteHeader(http.StatusNoContent)
return true
}

59
pkg/api/ip/filter.go Normal file
View file

@ -0,0 +1,59 @@
// Package ip is an API that deals with the blocking and unblocking of IPs on public servers
package ip
import (
"encoding/json"
"log"
"net/http"
"strconv"
"github.com/Ennovar/gPanel/pkg/database"
)
func Filter(res http.ResponseWriter, req *http.Request, logger *log.Logger, dir string) bool {
if req.Method != "POST" {
logger.Println(req.URL.Path + "::" + req.Method + "::" + strconv.Itoa(http.StatusMethodNotAllowed) + "::" + http.StatusText(http.StatusMethodNotAllowed))
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return false
}
var blockIPRequestData struct {
IP string `json:"ip"`
Type string `json:"type"`
}
err := json.NewDecoder(req.Body).Decode(&blockIPRequestData)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
if blockIPRequestData.Type != "maintenance" && blockIPRequestData.Type != "block" {
logger.Println(req.URL.Path + "::" + " filtered IP type is invalid")
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
ds, err := database.Open(dir + database.DB_MAIN)
if err != nil || ds == nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
defer ds.Close()
var newIP database.Struct_Filtered_IP
newIP.IP = blockIPRequestData.IP
newIP.Type = blockIPRequestData.Type
err = ds.NewFilteredIP(&newIP)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
res.WriteHeader(http.StatusNoContent)
return true
}

67
pkg/api/ip/list.go Normal file
View file

@ -0,0 +1,67 @@
package ip
import (
"encoding/json"
"log"
"net/http"
"strconv"
"github.com/Ennovar/gPanel/pkg/database"
)
func List(res http.ResponseWriter, req *http.Request, logger *log.Logger, dir string) bool {
if req.Method != "POST" {
logger.Println(req.URL.Path + "::" + req.Method + "::" + strconv.Itoa(http.StatusMethodNotAllowed) + "::" + http.StatusText(http.StatusMethodNotAllowed))
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return false
}
var listIPRequestData struct {
Type string `json:"type"`
}
err := json.NewDecoder(req.Body).Decode(&listIPRequestData)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
if listIPRequestData.Type != "maintenance" && listIPRequestData.Type != "block" {
logger.Println(req.URL.Path + "::" + " filtered IP type is invalid")
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
ds, err := database.Open(dir + database.DB_MAIN)
if err != nil || ds == nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
defer ds.Close()
list, err := ds.GetFilteredIPs(listIPRequestData.Type)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
if len(list) > 0 {
b, err := json.Marshal(list)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
res.WriteHeader(http.StatusOK)
res.Write(b)
return true
}
res.WriteHeader(http.StatusNoContent)
return true
}

7
pkg/api/ip/unfilter.go Normal file
View file

@ -0,0 +1,7 @@
package ip
import "net/http"
func Unfilter(res http.ResponseWriter, req *http.Request, dir string) bool {
return true
}

View file

@ -16,8 +16,18 @@ const (
// Bucket constants
const (
BUCKET_USERS = "users"
BUCKET_PORTS = "ports"
BUCKET_USERS = "users"
BUCKET_PORTS = "ports"
BUCKET_FILTERED_IPS = "filtered_ips"
)
// Database Structs
type (
Struct_Filtered_IP struct {
ID int `json:"id"`
Type string `json:"type"`
IP string `json:"ip"`
}
)
// Error codes
@ -45,12 +55,16 @@ func Open(filepath string) (*Datastore, error) {
// Ensure that all top-level buckets exist
err = ds.handle.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(BUCKET_USERS))
if err != nil {
return err
}
_, err = tx.CreateBucketIfNotExists([]byte(BUCKET_PORTS))
_, err = tx.CreateBucketIfNotExists([]byte(BUCKET_PORTS))
if err != nil {
return err
}
_, err = tx.CreateBucketIfNotExists([]byte(BUCKET_FILTERED_IPS))
if err != nil {
return err
}

View file

@ -0,0 +1,49 @@
package database
import (
"encoding/binary"
"encoding/json"
"github.com/boltdb/bolt"
)
func (ds *Datastore) NewFilteredIP(newip *Struct_Filtered_IP) error {
return ds.handle.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(BUCKET_FILTERED_IPS))
id, _ := b.NextSequence()
newip.ID = int(id)
buf, err := json.Marshal(newip)
if err != nil {
return err
}
key := make([]byte, 8)
binary.BigEndian.PutUint64(key, uint64(newip.ID))
return b.Put(key, buf)
})
}
func (ds *Datastore) GetFilteredIPs(iptype string) (map[int]Struct_Filtered_IP, error) {
filtered := make(map[int]Struct_Filtered_IP)
var holder Struct_Filtered_IP
ds.handle.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(BUCKET_FILTERED_IPS))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
json.Unmarshal(v, &holder)
if holder.Type == iptype {
filtered[holder.ID] = holder
}
}
return nil
})
return filtered, nil
}

View file

@ -5,6 +5,7 @@ import (
"net/http"
"strings"
"github.com/Ennovar/gPanel/pkg/api/ip"
logapi "github.com/Ennovar/gPanel/pkg/api/log"
"github.com/Ennovar/gPanel/pkg/api/server"
"github.com/Ennovar/gPanel/pkg/api/user"
@ -42,6 +43,10 @@ func (con *Controller) apiHandler(res http.ResponseWriter, req *http.Request) (b
return true, logapi.Read(res, req, con.APILogger, con.Directory)
case "/log/delete":
return true, logapi.Truncate(res, req, con.APILogger, con.Directory)
case "/ip/list":
return true, ip.List(res, req, con.APILogger, con.Directory)
case "/ip/filter":
return true, ip.Filter(res, req, con.APILogger, con.Directory)
default:
return false, false
}

View file

@ -3,22 +3,35 @@
package networking
import (
"bytes"
"net/http"
"regexp"
"strings"
)
// GetClientIP returns the current client's IP as an array of bytes.
// BUG(george-e-shaw-iv) Uses an external API
func GetClientIP() ([]byte, error) {
resp, err := http.Get("http://myexternalip.com/raw")
// GetClientIP returns the current client's IP
func GetClientIP(req *http.Request) string {
var addr string
regex := regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`)
if err != nil {
return []byte{}, err
if fwd := req.Header.Get(http.CanonicalHeaderKey("X-Forwarded-For")); fwd != "" {
s := strings.Index(fwd, ", ")
if s == -1 {
s = len(fwd)
}
addr = fwd[:s]
} else if fwd := req.Header.Get(http.CanonicalHeaderKey("X-Real-IP")); fwd != "" {
addr = fwd
} else if fwd := req.Header.Get(http.CanonicalHeaderKey("Forwarded")); fwd != "" {
if match := regex.FindStringSubmatch(fwd); len(match) > 1 {
addr = strings.Trim(match[1], `"`)
}
} else {
addr = strings.Split(req.RemoteAddr, ":")[0]
}
defer resp.Body.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
return buf.Bytes(), nil
return addr
}

View file

@ -19,10 +19,9 @@ jQuery('._js_bundles-manage').on('click', function(e){
else {
manageBundlesModal.find('.modal-body').html("An error has occurred. Please try again. If problem persists contact server administrator.")
}
manageBundlesModal.modal('show');
}
else if(xhr.status == 204) {
manageBundlesModal.modal('show');
manageBundlesModal.find('.modal-body').html("<p>No bundles current exist on the server.</p>")
}
else {
if(xhr.response != undefined && xhr.response.length != 0) {
@ -31,7 +30,8 @@ jQuery('._js_bundles-manage').on('click', function(e){
else {
manageBundlesModal.find('.modal-body').html(xhr.status + " Error!")
}
manageBundlesModal.modal('show');
}
}
manageBundlesModal.modal('show');
});

View file

@ -74,9 +74,7 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>No bundles current exist on the server.</p>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>