Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial checkout from https://github.com/Humbedooh/blocky2
- Loading branch information
0 parents
commit 882378b
Showing
89 changed files
with
15,107 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
client: | ||
interval: 60 | ||
|
||
server: | ||
apiurl: https://blocky.foo.org/blocky/api | ||
# bla bla bla | ||
#legacyurl: https://blocky.apache.org/blocky.json | ||
|
||
iptables: | ||
chains: | ||
- INPUT | ||
- fail2ban-default |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# Licensed to the Apache Software Foundation (ASF) under one or more | ||
# contributor license agreements. See the NOTICE file distributed with | ||
# this work for additional information regarding copyright ownership. | ||
# The ASF licenses this file to You under the Apache License, Version 2.0 | ||
# (the "License"); you may not use this file except in compliance with | ||
# the License. You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
""" | ||
This is the main WSGI handler file for Blocky. | ||
It compiles a list of valid URLs from the 'pages' library folder, | ||
and if a URL matches it runs the specific submodule's run() function. It | ||
also handles CGI parsing and exceptions in the applications. | ||
""" | ||
|
||
|
||
# Main imports | ||
import cgi | ||
import re | ||
import sys | ||
import traceback | ||
import yaml | ||
import json | ||
import plugins.session | ||
import plugins.database | ||
import plugins.openapi | ||
import plugins.worker | ||
import atexit | ||
import os | ||
import time | ||
import threading | ||
|
||
|
||
# Hack to start first worker as a background thread | ||
ppid = os.getppid() | ||
now = time.time() | ||
pidfile = "./blocky-bg-%s.pid" % ppid | ||
background = False | ||
|
||
def rmpidfile(): | ||
try: | ||
print("Removing BG thread pid file...") | ||
os.unlink(pidfile) | ||
except: | ||
pass | ||
|
||
if not os.path.exists(pidfile) or os.path.getmtime(pidfile) < (now - 60): | ||
print("Starting this thread as background worker") | ||
open(pidfile, "w").write(str(os.getpid())) | ||
atexit.register(rmpidfile) | ||
background = True | ||
|
||
# Compile valid API URLs from the pages library | ||
# Allow backwards compatibility by also accepting .lua URLs | ||
urls = [] | ||
if __name__ != '__main__': | ||
import pages | ||
for page in pages.handlers: | ||
urls.append((r"^(/api/%s)(/.+)?$" % page, pages.handlers[page].run)) | ||
|
||
|
||
# Load Blocky master configuration | ||
config = yaml.load(open("yaml/blocky.yaml")) | ||
|
||
# Instantiate database connections | ||
DB = plugins.database.BlockyDatabase(config) | ||
|
||
# Load Open API specifications | ||
BlockyOpenAPI = plugins.openapi.OpenAPI("yaml/openapi.yaml") | ||
|
||
# Start as background worker? | ||
if background: | ||
t = threading.Thread(target = plugins.worker.start, args = (DB, config, pidfile)) | ||
t.start() | ||
|
||
|
||
class BlockyAPIWrapper: | ||
""" | ||
Middleware wrapper for exceptions in the application | ||
""" | ||
def __init__(self, path, func): | ||
self.func = func | ||
self.API = BlockyOpenAPI | ||
self.path = path | ||
self.exception = plugins.openapi.BlockyHTTPError | ||
|
||
def __call__(self, environ, start_response, session): | ||
"""Run the function, return response OR return stacktrace""" | ||
response = None | ||
|
||
# CORS IGNORE | ||
if environ.get('REQUEST_METHOD', 'GET') == 'OPTIONS': | ||
start_response('200 Fine, whatever', [ | ||
('Content-Type', 'application/json')]) | ||
yield json.dumps({ | ||
"code": 200, | ||
"reason": "You're probably just being silly..." | ||
}) | ||
return | ||
|
||
try: | ||
# Read JSON client data if any | ||
try: | ||
request_size = int(environ.get('CONTENT_LENGTH', 0)) | ||
except (ValueError): | ||
request_size = 0 | ||
requestBody = environ['wsgi.input'].read(request_size) | ||
formdata = {} | ||
if requestBody and len(requestBody) > 0: | ||
try: | ||
formdata = json.loads(requestBody.decode('utf-8')) | ||
|
||
except json.JSONDecodeError as err: | ||
start_response('400 Invalid request', [ | ||
('Content-Type', 'application/json')]) | ||
yield json.dumps({ | ||
"code": 400, | ||
"reason": "Invalid JSON: %s" % err | ||
}) | ||
return | ||
else: | ||
# Try query string?? | ||
xdata = cgi.parse_qs(environ.get('QUERY_STRING')) | ||
for k, v in xdata.items(): | ||
formdata[k] = v[0] | ||
|
||
|
||
# Validate URL against OpenAPI specs | ||
try: | ||
self.API.validate(environ['REQUEST_METHOD'], self.path, formdata) | ||
except plugins.openapi.OpenAPIException as err: | ||
start_response('400 Invalid request', [ | ||
('Content-Type', 'application/json')]) | ||
yield json.dumps({ | ||
"code": 400, | ||
"reason": err.message | ||
}) | ||
return | ||
|
||
# Call page with env, SR and form data | ||
try: | ||
response = self.func(self, environ, formdata, session) | ||
if response: | ||
for bucket in response: | ||
yield bucket | ||
except plugins.openapi.BlockyHTTPError as err: | ||
errHeaders = { | ||
403: '403 Authentication failed', | ||
404: '404 Resource not found', | ||
500: '500 Internal Server Error', | ||
501: '501 Gateway error' | ||
} | ||
errHeader = errHeaders[err.code] if err.code in errHeaders else "400 Bad request" | ||
start_response(errHeader, [ | ||
('Content-Type', 'application/json')]) | ||
yield json.dumps({ | ||
"code": err.code, | ||
"reason": err.message | ||
}, indent = 4) + "\n" | ||
return | ||
|
||
except: | ||
err_type, err_value, tb = sys.exc_info() | ||
traceback_output = ['API traceback:'] | ||
traceback_output += traceback.format_tb(tb) | ||
traceback_output.append('%s: %s' % (err_type.__name__, err_value)) | ||
# We don't know if response has been given yet, try giving one, fail gracefully. | ||
try: | ||
start_response('500 Internal Server Error', [ | ||
('Content-Type', 'application/json')]) | ||
except: | ||
pass | ||
yield json.dumps({ | ||
"API": "Blocky API 1.0", | ||
"code": "500", | ||
"reason": '\n'.join(traceback_output) | ||
}) | ||
|
||
|
||
def fourohfour(environ, start_response): | ||
"""A very simple 404 handler""" | ||
start_response("404 Not Found", [ | ||
('Content-Type', 'application/json')]) | ||
yield json.dumps({ | ||
"API": "Blocky API 2.0", | ||
"code": 404, | ||
"reason": "API endpoint not found" | ||
}, indent = 4) + "\n" | ||
return | ||
|
||
|
||
def application(environ, start_response): | ||
""" | ||
This is the main handler. Every API call goes through here. | ||
Checks against the pages library, and if submod found, runs | ||
it and returns the output. | ||
""" | ||
path = environ.get('PATH_INFO', '') | ||
for regex, function in urls: | ||
m = re.match(regex, path) | ||
if m: | ||
callback = BlockyAPIWrapper(path, function) | ||
session = plugins.session.BlockySession(DB, environ, config) | ||
a = 0 | ||
for bucket in callback(environ, start_response, session): | ||
if a == 0: | ||
session.headers.append(bucket) | ||
try: | ||
start_response("200 Okay", session.headers) | ||
except: | ||
pass | ||
a += 1 | ||
# WSGI prefers byte strings, so convert if regular py3 string | ||
if isinstance(bucket, str): | ||
yield bytes(bucket, encoding = 'utf-8') | ||
elif isinstance(bucket, bytes): | ||
yield bucket | ||
return | ||
|
||
for bucket in fourohfour(environ, start_response): | ||
yield bytes(bucket, encoding = 'utf-8') | ||
|
||
|
||
|
||
if __name__ == '__main__': | ||
BlockyOpenAPI.toHTML() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# | ||
# Licensed to the Apache Software Foundation (ASF) under one or more | ||
# contributor license agreements. See the NOTICE file distributed with | ||
# this work for additional information regarding copyright ownership. | ||
# The ASF licenses this file to You under the Apache License, Version 2.0 | ||
# (the "License"); you may not use this file except in compliance with | ||
# the License. You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
""" | ||
Kibble API scripts library: | ||
oauth: oauth manager | ||
""" | ||
|
||
import importlib | ||
import os | ||
# Define all the submodules we have | ||
|
||
rootpath = os.path.dirname(__file__) | ||
print("Reading pages from %s" % rootpath) | ||
|
||
# Import each submodule into a hash called 'handlers' | ||
handlers = {} | ||
|
||
def loadPage(path): | ||
for el in os.listdir(path): | ||
filepath = os.path.join(path, el) | ||
if el.find("__") == -1: | ||
if os.path.isdir(filepath): | ||
loadPage(filepath) | ||
else: | ||
p = filepath.replace(rootpath, "")[1:].replace('/', '.')[:-3] | ||
xp = p.replace('.', '/') | ||
#print("Loading endpoint pages.%s as %s" % (p, xp)) | ||
handlers[xp] = importlib.import_module("pages.%s" % p) | ||
|
||
loadPage(rootpath) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# Licensed to the Apache Software Foundation (ASF) under one or more | ||
# contributor license agreements. See the NOTICE file distributed with | ||
# this work for additional information regarding copyright ownership. | ||
# The ASF licenses this file to You under the Apache License, Version 2.0 | ||
# (the "License"); you may not use this file except in compliance with | ||
# the License. You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
######################################################################## | ||
# OPENAPI-URI: /api/activity | ||
######################################################################## | ||
# get: | ||
# responses: | ||
# '200': | ||
# content: | ||
# application/json: | ||
# schema: | ||
# $ref: '#/components/schemas/Empty' | ||
# description: 200 response | ||
# default: | ||
# content: | ||
# application/json: | ||
# schema: | ||
# $ref: '#/components/schemas/Error' | ||
# description: unexpected error | ||
# security: | ||
# - cookieAuth: [] | ||
# summary: Displays the current activity entries | ||
# | ||
######################################################################## | ||
|
||
|
||
|
||
""" | ||
This is the activity handler for Blocky/2 | ||
""" | ||
|
||
import json | ||
import re | ||
import time | ||
import bcrypt | ||
import hashlib | ||
import plugins.worker | ||
|
||
def run(API, environ, indata, session): | ||
global WHITE_CACHE, WHITE_TS | ||
method = environ['REQUEST_METHOD'] | ||
|
||
# Display the current activity entries | ||
if method == "GET": | ||
activity = [] | ||
res = session.DB.ES.search( | ||
index=session.DB.dbname, | ||
doc_type="note", | ||
size = 500, | ||
body = { | ||
'query': { | ||
'match_all': {} | ||
}, | ||
'sort': { | ||
"epoch": "desc" | ||
} | ||
} | ||
) | ||
|
||
for hit in res['hits']['hits']: | ||
doc = hit['_source'] | ||
if doc['ntype'] in ['manual', 'client', 'system', 'autoban']: | ||
activity.append(doc) | ||
|
||
JSON_OUT = { | ||
'activity': activity | ||
} | ||
yield json.dumps(JSON_OUT) | ||
return | ||
|
||
# Finally, if we hit a method we don't know, balk! | ||
yield API.exception(400, "I don't know this request method!!") | ||
|
Oops, something went wrong.