Skip to content

Commit

Permalink
Initial checkout from https://github.com/Humbedooh/blocky2
Browse files Browse the repository at this point in the history
  • Loading branch information
Humbedooh committed Mar 27, 2019
0 parents commit 882378b
Show file tree
Hide file tree
Showing 89 changed files with 15,107 additions and 0 deletions.
488 changes: 488 additions & 0 deletions client/blocky.py

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions client/blocky.yaml
@@ -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
503 changes: 503 additions & 0 deletions server/api/api.html

Large diffs are not rendered by default.

235 changes: 235 additions & 0 deletions server/api/handler.py
@@ -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()
46 changes: 46 additions & 0 deletions server/api/pages/__init__.py
@@ -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)
87 changes: 87 additions & 0 deletions server/api/pages/activity.py
@@ -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!!")

0 comments on commit 882378b

Please sign in to comment.