From b8ade3bd108235829707f6795cdf7461b6e0e69b Mon Sep 17 00:00:00 2001 From: root Date: Fri, 23 Jul 2021 19:28:19 +0200 Subject: [PATCH 1/2] Wordpress api --- admin/src/admin/__init__.py | 1 + admin/src/admin/auth/authentication.py | 5 ++ admin/src/admin/views/ApiViews.py | 4 -- admin/src/admin/views/InternalViews.py | 83 ++++++++++++++++++++++++++ admin/src/admin/views/decorators.py | 18 ++++++ admin/src/wordpress_saml_onlycerts.py | 2 +- docker/haproxy/haproxy.conf | 4 ++ 7 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 admin/src/admin/views/InternalViews.py create mode 100644 admin/src/admin/views/decorators.py diff --git a/admin/src/admin/__init__.py b/admin/src/admin/__init__.py index f5ed74d..bf623a5 100644 --- a/admin/src/admin/__init__.py +++ b/admin/src/admin/__init__.py @@ -87,6 +87,7 @@ Import all views from .views import LoginViews from .views import WebViews from .views import ApiViews +from .views import InternalViews diff --git a/admin/src/admin/auth/authentication.py b/admin/src/admin/auth/authentication.py index 26b5a70..b89d42e 100644 --- a/admin/src/admin/auth/authentication.py +++ b/admin/src/admin/auth/authentication.py @@ -36,6 +36,11 @@ ram_users={ 'id': os.environ["KEYCLOAK_USER"], 'password': os.environ["KEYCLOAK_PASSWORD"], 'role': 'admin', + }, + os.environ["WORDPRESS_MARIADB_USER"]: { + 'id': os.environ["WORDPRESS_MARIADB_USER"], + 'password': os.environ["WORDPRESS_MARIADB_PASSWORD"], + 'role': 'manager', } } diff --git a/admin/src/admin/views/ApiViews.py b/admin/src/admin/views/ApiViews.py index e11a7ac..9c3287c 100644 --- a/admin/src/admin/views/ApiViews.py +++ b/admin/src/admin/views/ApiViews.py @@ -15,8 +15,6 @@ import threading threads={} # q = Queue.Queue() -from pprint import pprint - from keycloak.exceptions import KeycloakGetError @app.route('/api/resync') @@ -99,8 +97,6 @@ def groups(provider=False): if provider == 'keycloak': return json.dumps(app.admin.delete_keycloak_groups()), 200, {'Content-Type': 'application/json'} - - ### SYSADM USERS ONLY @app.route('/api/external', methods=['POST', 'PUT', 'GET','DELETE']) diff --git a/admin/src/admin/views/InternalViews.py b/admin/src/admin/views/InternalViews.py new file mode 100644 index 0000000..f33fd63 --- /dev/null +++ b/admin/src/admin/views/InternalViews.py @@ -0,0 +1,83 @@ +#!flask/bin/python +# coding=utf-8 +from admin import app +import logging as log +import traceback + +import time,json +import sys,os + +from flask import request +from .decorators import is_internal + +@app.route('/api/internal/users', methods=['GET']) +@is_internal +def internal_users(): + if request.method == 'GET': + sorted_users = sorted(app.admin.get_mix_users(), key=lambda k: k['username']) + # group_users = [user for user in sorted_users if data['path'] in user['keycloak_groups']] + users=[] + for user in sorted_users: + if not user['enabled']: continue + users.append(user_parser(user)) + return json.dumps(users), 200, {'Content-Type': 'application/json'} + +@app.route('/api/internal/users/filter', methods=['POST']) +@is_internal +def internal_users_search(): + if request.method == 'POST': + data=request.get_json(force=True) + users = app.admin.get_mix_users() + result = [user_parser(user) for user in users + if data['text'] in user['username'] or + data['text'] in user['first'] or + data['text'] in user['last'] or + data['text'] in user['email']] + sorted_result = sorted(result, key=lambda k: k['id']) + return json.dumps(sorted_result), 200, {'Content-Type': 'application/json'} + +@app.route('/api/internal/groups', methods=['GET']) +@is_internal +def internal_groups(): + if request.method == 'GET': + sorted_groups = sorted(app.admin.get_mix_groups(), key=lambda k: k['name']) + groups=[] + for group in sorted_groups: + if not group['path'].startswith('/'): continue + groups.append({'id':group['path'], + 'name':group['name'], + 'description':group.get('description','')}) + return json.dumps(groups), 200, {'Content-Type': 'application/json'} + +@app.route('/api/internal/group/users', methods=['POST']) +@is_internal +def internal_group_users(): + if request.method == 'POST': + data=request.get_json(force=True) + sorted_users = sorted(app.admin.get_mix_users(), key=lambda k: k['username']) + # group_users = [user for user in sorted_users if data['path'] in user['keycloak_groups']] + users=[] + for user in sorted_users: + if data['path'] not in user['keycloak_groups'] or not user['enabled']: continue + users.append(user_parser(user)) + return json.dumps(users), 200, {'Content-Type': 'application/json'} + +@app.route('/api/internal/roles', methods=['GET']) +@is_internal +def internal_roles(): + if request.method == 'GET': + roles=[] + for role in sorted(app.admin.get_roles(), key=lambda k: k['name']): + if role['name'] == 'admin': continue + roles.append({'id':role['id'], + 'name':role['name'], + 'description':role.get('description','')}) + return json.dumps(roles), 200, {'Content-Type': 'application/json'} + +def user_parser(user): + return {'id':user['username'], + 'first':user['first'], + 'last':user['last'], + 'role':user['roles'][0] if len(user['roles']) else None, + 'email':user['email'], + 'groups':user['keycloak_groups']} diff --git a/admin/src/admin/views/decorators.py b/admin/src/admin/views/decorators.py new file mode 100644 index 0000000..3b6c867 --- /dev/null +++ b/admin/src/admin/views/decorators.py @@ -0,0 +1,18 @@ +#!flask/bin/python +# coding=utf-8 + +from functools import wraps +from flask import request, redirect, url_for +from flask_login import logout_user +import socket + +def is_internal(fn): + @wraps(fn) + def decorated_view(*args, **kwargs): + remote_addr=request.headers['X-Forwarded-For'].split(',')[0] if 'X-Forwarded-For' in request.headers else request.remote_addr.split(',')[0] + ## Now only checks if it is wordpress container, + ## but we should check if it is internal net and not haproxy + if socket.gethostbyname('isard-apps-wordpress') == remote_addr: return fn(*args, **kwargs) + logout_user() + return redirect(url_for('login')) + return decorated_view \ No newline at end of file diff --git a/admin/src/wordpress_saml_onlycerts.py b/admin/src/wordpress_saml_onlycerts.py index b1e64cd..2cbc39c 100644 --- a/admin/src/wordpress_saml_onlycerts.py +++ b/admin/src/wordpress_saml_onlycerts.py @@ -126,4 +126,4 @@ class WordpressSaml(): self.db.update("""DELETE FROM wp_options WHERE option_name = 'onelogin_saml_advanced_settings_sp_x509cert'""") self.db.update("""DELETE FROM wp_options WHERE option_name = 'onelogin_saml_advanced_settings_sp_privatekey'""") -nw=WordpressSaml() \ No newline at end of file +nw=WordpressSaml() diff --git a/docker/haproxy/haproxy.conf b/docker/haproxy/haproxy.conf index 93e2a0a..ad5a37c 100644 --- a/docker/haproxy/haproxy.conf +++ b/docker/haproxy/haproxy.conf @@ -166,6 +166,10 @@ backend be_wp acl existing-x-forwarded-proto req.hdr(X-Forwarded-Proto) -m found http-request add-header X-Forwarded-Host %[req.hdr(Host)] unless existing-x-forwarded-host http-request add-header X-Forwarded-Proto https unless existing-x-forwarded-proto + + http-request set-header X-SSL %[ssl_fc] + #reqadd X-Forwarded-Proto:\ https + http-request set-header X-Forwarded-Proto https server wp isard-apps-wordpress:80 check port 80 inter 5s rise 2 fall 10 resolvers mydns init-addr none From 5048236e4ec6a7f45e55f5243534d7a491282511 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 24 Aug 2021 12:01:42 +0200 Subject: [PATCH 2/2] Added filter for roles and groups --- admin/src/admin/lib/admin.py | 16 +++++++---- admin/src/admin/lib/nextcloud.py | 5 +++- admin/src/admin/lib/nextcloud_exc.py | 3 ++ admin/src/admin/views/InternalViews.py | 38 +++++++++++++++++++++----- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/admin/src/admin/lib/admin.py b/admin/src/admin/lib/admin.py index d98114e..41fa9c7 100644 --- a/admin/src/admin/lib/admin.py +++ b/admin/src/admin/lib/admin.py @@ -93,13 +93,17 @@ class Admin(): ### User admin in group admin try: log.warning('KEYCLOAK: Adding group admin and user admin to this group') - self.keycloak.add_group('admin') - ## Add default admin user to group admin (for nextcloud, just in case we go there) - admin_uid=self.keycloak_admin.get_user_id('admin') - self.keycloak_admin.group_user_add(admin_uid,gid) + admin_guid=self.keycloak.add_group('admin') + except: + pass + admin_guid=self.keycloak.get_group_by_path(path='/admin')['id'] + try: + ## Add default admin user to group admin + admin_uid=self.keycloak.get_user_id('admin') + self.keycloak.group_user_add(admin_uid,admin_guid) log.warning('KEYCLOAK: OK') except: - # print(traceback.format_exc()) + print(traceback.format_exc()) log.warning('KEYCLOAK: Seems to be there already') #### Add default groups @@ -812,4 +816,4 @@ class Admin(): return True def get_user(self,userid): - return [u for u in self.internal['users'] if u['id']==userid][0] \ No newline at end of file + return [u for u in self.internal['users'] if u['id']==userid][0] diff --git a/admin/src/admin/lib/nextcloud.py b/admin/src/admin/lib/nextcloud.py index c3bab4d..65677d7 100644 --- a/admin/src/admin/lib/nextcloud.py +++ b/admin/src/admin/lib/nextcloud.py @@ -29,7 +29,10 @@ class Nextcloud(): def _request(self,method,url,data={},headers={'OCS-APIRequest':'true'},auth=False): if auth == False: auth=self.auth try: - return requests.request(method, url, data=data, auth=auth, verify=self.verify_cert, headers=headers).text + response = requests.request(method, url, data=data, auth=auth, verify=self.verify_cert, headers=headers) + if 'meta' in response.text: + if '997' in response.text: raise ProviderUnauthorized + return response.text ## At least the ProviderSslError is not being catched or not raised correctly except requests.exceptions.HTTPError as errh: diff --git a/admin/src/admin/lib/nextcloud_exc.py b/admin/src/admin/lib/nextcloud_exc.py index 32baf33..827e93f 100644 --- a/admin/src/admin/lib/nextcloud_exc.py +++ b/admin/src/admin/lib/nextcloud_exc.py @@ -1,5 +1,8 @@ #!/usr/bin/env python # coding=utf-8 +class ProviderUnauthorized(Exception): + pass + class ProviderConnError(Exception): pass diff --git a/admin/src/admin/views/InternalViews.py b/admin/src/admin/views/InternalViews.py index f33fd63..c9916d9 100644 --- a/admin/src/admin/views/InternalViews.py +++ b/admin/src/admin/views/InternalViews.py @@ -28,11 +28,7 @@ def internal_users_search(): if request.method == 'POST': data=request.get_json(force=True) users = app.admin.get_mix_users() - result = [user_parser(user) for user in users - if data['text'] in user['username'] or - data['text'] in user['first'] or - data['text'] in user['last'] or - data['text'] in user['email']] + result = [user_parser(user) for user in filter_users(users, data['text'])] sorted_result = sorted(result, key=lambda k: k['id']) return json.dumps(sorted_result), 200, {'Content-Type': 'application/json'} @@ -59,8 +55,12 @@ def internal_group_users(): users=[] for user in sorted_users: if data['path'] not in user['keycloak_groups'] or not user['enabled']: continue - users.append(user_parser(user)) - return json.dumps(users), 200, {'Content-Type': 'application/json'} + users.append(user) + if data.get('text',False) and data['text'] != '': + result = [user_parser(user) for user in filter_users(users, data['text'])] + else: + result = [user_parser(user) for user in users] + return json.dumps(result), 200, {'Content-Type': 'application/json'} @app.route('/api/internal/roles', methods=['GET']) @is_internal @@ -74,6 +74,23 @@ def internal_roles(): 'description':role.get('description','')}) return json.dumps(roles), 200, {'Content-Type': 'application/json'} +@app.route('/api/internal/role/users', methods=['POST']) +@is_internal +def internal_role_users(): + if request.method == 'POST': + data=request.get_json(force=True) + sorted_users = sorted(app.admin.get_mix_users(), key=lambda k: k['username']) + # group_users = [user for user in sorted_users if data['path'] in user['keycloak_groups']] + users=[] + for user in sorted_users: + if data['role'] not in user['roles'] or not user['enabled']: continue + users.append(user) + if data.get('text',False) and data['text'] != '': + result = [user_parser(user) for user in filter_users(users, data['text'])] + else: + result = [user_parser(user) for user in users] + return json.dumps(result), 200, {'Content-Type': 'application/json'} + def user_parser(user): return {'id':user['username'], 'first':user['first'], @@ -81,3 +98,10 @@ def user_parser(user): 'role':user['roles'][0] if len(user['roles']) else None, 'email':user['email'], 'groups':user['keycloak_groups']} + +def filter_users(users, text): + return [user for user in users + if text in user['username'] or + text in user['first'] or + text in user['last'] or + text in user['email']] \ No newline at end of file