def api_newapp(): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None # Reject non-JSON payload if not request.json: raise error_handlers.InvalidAPIUsage("bad payload format", status_code=400) request_content = request.get_json() if 'appname' not in request_content: raise error_handlers.InvalidAPIUsage("bad payload", status_code=400) # Default to read-only apps app_attributes = {"writeEnabled": "False"} if 'writeenabled' in request_content: if request_content['writeenabled'] is True: app_attributes['writeEnabled'] = "True" app_name = html.escape(request_content['appname']) try: app_id, app_key, app_secret = jots.pyauth.app.create_app( app_name, attributes=app_attributes, db=DB_CON) return jsonify( {app_name: { "id": app_id, "key": app_key, "secret": app_secret }}) except jots.pyauth.app.AppActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400)
def api_passwordreset(): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None # Reject non-JSON payload if not request.json: raise error_handlers.InvalidAPIUsage("bad payload format", status_code=400) request_content = request.get_json() if 'email' not in request_content: raise error_handlers.InvalidAPIUsage("bad payload", status_code=400) email = html.escape(request_content['email']) if len(email) < 5: raise error_handlers.InvalidAPIUsage("bad email address", status_code=400) try: user = jots.pyauth.user.user(email_address=email, db=DB_CON) except jots.pyauth.user.UserNotFound: raise error_handlers.InvalidAPIUsage("bad user", status_code=400) except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) try: reset_code = user.reset_password( service_domain=app.config['DOMAIN_NAME']) except jots.pyauth.user.UserActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) # The next step is to email a link to the 'reset' page with a query string (q) containing reset code try: email_obj = mailer.personalised_email( recipient=email, template_name="reset", data={ "site_name": app.config['DOMAIN_NAME'], "reset_url": "https://{}:{}/reset?q={}".format(app.config['DOMAIN_NAME'], app.config['SERVER_PORT'], reset_code) }) if app.config['TESTING']: email_obj.send(mail_agent="file") else: email_obj.send() except mailer.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except mailer.MailActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) return jsonify({"status": "ok", "reset_code": reset_code})
def app_req_rw_wrapper(*args, **kwargs): if g.app_obj is not None: if 'writeEnabled' not in g.app_obj.properties.attributes: raise error_handlers.InvalidAPIUsage("access denied", status_code=403) if not bool( strtobool( g.app_obj.properties.attributes['writeEnabled'])): raise error_handlers.InvalidAPIUsage("access denied", status_code=403) return func(*args, **kwargs)
def id_wrapper(*args, **kwargs): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None g.app_obj = None g.user_obj = None # Extract requesters identidy and confirm it's a valid app requester_id = get_jwt_identity() # App errors passed as we need to check that a user isn't authing before raising an error try: g.app_obj = jots.pyauth.app.app(app_name=requester_id, db=DB_CON) except jots.pyauth.app.AppNotFound: pass except jots.pyauth.user.InputError: pass try: g.user_obj = jots.pyauth.user.user(email_address=requester_id, db=DB_CON) except jots.pyauth.user.UserNotFound: pass except jots.pyauth.user.InputError: pass # Catch all - if neither object is populated, raise error if g.app_obj is None and g.user_obj is None: raise error_handlers.InvalidAPIUsage("access denied", status_code=403) return func(*args, **kwargs)
def api_get_appdetails(app_id): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None app_id = html.escape(app_id) try: app_object = jots.pyauth.app.app(app_id=app_id, db=DB_CON) except jots.pyauth.app.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except jots.pyauth.app.AppNotFound as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) return app_object.properties.as_dict()
def api_user_details(user_id): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None user_id = html.escape(user_id) try: user = jots.pyauth.user.user(user_id=user_id, db=DB_CON) return jsonify(user.properties.as_dict()) except jots.pyauth.user.UserNotFound: raise error_handlers.InvalidAPIUsage("invalid user", status=403) except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400)
def api_groupmembers(group_id): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None group_id = html.escape(group_id) try: group = jots.pyauth.group.group(group_id=group_id, db=DB_CON) group_members_with_email = group.get_members_detail(attribute="email") return jsonify(group_members_with_email) except jots.pyauth.group.GroupNotFound: return jsonify(dict()) except jots.pyauth.group.GroupActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except jots.pyauth.group.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400)
def usr_adm_wrapper(*args, **kwargs): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None if g.user_obj is not None: # If user object was created, check user is admin try: group = jots.pyauth.group.group(group_name="admin", db=DB_CON) except jots.pyauth.group.GroupNotFound: raise error_handlers.InvalidAPIUsage("admin group not found", status_code=500) if g.user_obj.properties.userId not in group.properties.members: raise error_handlers.InvalidAPIUsage("access denied", status_code=403) return func(*args, **kwargs)
def api_findgroups(): ''' Search for groups based on either group name or a user ID The two use different methods wihin the group moduel but should result in the same output ''' # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None # Reject non-JSON payload if not request.json: raise error_handlers.InvalidAPIUsage("bad payload format", status_code=400) request_content = request.get_json() # Search by group name if 'groupname' in request_content: groupname = html.escape(request_content['groupname']) try: response = jots.pyauth.group.find_groups_like(groupname, db=DB_CON) return jsonify(response) except jots.pyauth.group.GroupNotFound: return jsonify(dict()) # Search for group by member user id elif 'userid' in request_content: userid = html.escape(request_content['userid']) try: response = jots.pyauth.group.find_user_in_group(userid, db=DB_CON) return jsonify(response) except jots.pyauth.group.GroupNotFound: return jsonify(dict()) else: raise error_handlers.InvalidAPIUsage("bad payload", status_code=400)
def token_get(): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None if 'Authorization' not in request.headers: raise error_handlers.InvalidAPIUsage("missing autorization header", status_code=400) auth_header = request.headers.get('Authorization') if "Basic " in auth_header: auth_header = auth_header.split("Basic ")[1] b64_auth_content = base64.b64decode(auth_header).decode('utf-8').strip() if ":" not in b64_auth_content: raise error_handlers.InvalidAPIUsage("bad autorization header", status_code=400) key, secret = b64_auth_content.split(":") try: app_obj = jots.pyauth.app.app(app_key=key, db=DB_CON) except jots.pyauth.app.AppNotFound: raise error_handlers.InvalidAPIUsage("forbidden - bad app", status_code=403) except jots.pyauth.app.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) if app_obj.authenticate(secret): expires = datetime.timedelta(days=30) access_token = create_access_token( identity=app_obj.properties.appName, expires_delta=expires, user_claims={"appId": app_obj.properties.appId}) return access_token else: raise error_handlers.InvalidAPIUsage("permission denied", status_code=403)
def api_newgroup(): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None # Reject non-JSON payload if not request.json: raise error_handlers.InvalidAPIUsage("bad payload format", status_code=400) request_content = request.get_json() if 'groupname' not in request_content: raise error_handlers.InvalidAPIUsage("bad payload", status_code=400) group_name = html.escape(request_content['groupname']) try: new_group = jots.pyauth.group.create_group(group_name, db=DB_CON) return jsonify(new_group) except jots.pyauth.group.GroupActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400)
def login_form(): ''' Accepts login form data Get user object and check password Returns access and refresh tokens with CSRF tokens ''' # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None form_data = request.form username = html.escape(form_data['username']) password = form_data['password'] try: user = jots.pyauth.user.user(email_address=username, db=DB_CON) except jots.pyauth.user.UserNotFound: raise error_handlers.InvalidAPIUsage("access denied", status_code=403) except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) if user.properties.status != "active": raise error_handlers.InvalidAPIUsage("access denied", status_code=403) result = user.authenticate(password) if not result: raise error_handlers.InvalidAPIUsage("access denied", status_code=403) # Add user claims - groups, all except admin group_data = jots.pyauth.group.find_user_in_group(user.properties.userId, db=DB_CON) group_names = list() for group in group_data.keys(): if group != "admin": group_names.append(group) access_token = create_access_token(identity=username, user_claims={"groups": group_names}) refresh_token = create_refresh_token(identity=username) refresh_jti = get_jti(refresh_token) try: user.set_refresh_jti(refresh_jti) except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except jots.pyauth.user.UserActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) response = make_response(redirect("/page")) set_access_cookies(response, access_token) set_refresh_cookies(response, refresh_token) return response
def api_user_delete(): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None # Reject non-JSON payload if not request.json: raise error_handlers.InvalidAPIUsage("bad payload format", status_code=400) request_content = request.get_json() if "userid" not in request_content: raise error_handlers.InvalidAPIUsage("bad payload", status_code=400) user_id = html.escape(request_content['userid']) try: result = jots.pyauth.user.delete_user(user_id, db=DB_CON) return jsonify({"result": str(result)}) except jots.pyauth.user.UserActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400)
def reset_form(): ''' Accepts password reset form data Validates reset code and sets password Returns to login if OK ''' # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None valid_reset_status = ['new', 'reset'] form_data = request.form username = html.escape(form_data['username']) password = form_data['password'] reset_code = html.escape(form_data['resetcode']) try: user = jots.pyauth.user.user(email_address=username, db=DB_CON) except jots.pyauth.user.UserNotFound: raise error_handlers.InvalidAPIUsage("access denied", status_code=403) except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) # Check user status is suitable for a password reset if user.properties.status not in valid_reset_status: raise error_handlers.InvalidAPIUsage("password not resetable", status_code=400) # Check for matching reset code if user.properties.resetCode != reset_code: raise error_handlers.InvalidAPIUsage("invalid reset code", status_code=403) # Check reset code hasn't expired date_now = datetime.datetime.now() reset_expiry = datetime.datetime.strptime(user.properties.resetExpiry, "%d/%m/%YT%H:%M:%S.%f") if date_now > reset_expiry: raise error_handlers.InvalidAPIUsage("expired reset code", status_code=403) try: user.set_password(password) response = make_response(redirect("/")) return response except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except jots.pyauth.user.UserActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400)
def api_findapps(): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None # Reject non-JSON payload if not request.json: raise error_handlers.InvalidAPIUsage("bad payload format", status_code=400) request_content = request.get_json() if 'appname' not in request_content: raise error_handlers.InvalidAPIUsage("bad payload", status_code=400) app_name = html.escape(request_content['appname']) try: response = jots.pyauth.app.find_apps_like(app_name, db=DB_CON) return jsonify(response) except jots.pyauth.app.AppNotFound: return jsonify(dict())
def api_findusers(): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None # Reject non-JSON payload if not request.json: raise error_handlers.InvalidAPIUsage("bad payload format", status_code=400) request_content = request.get_json() if 'email' not in request_content: raise error_handlers.InvalidAPIUsage("bad payload", status_code=400) email_address = html.escape(request_content['email']) try: response = jots.pyauth.user.find_users_like(email_address, db=DB_CON) return jsonify(response) except jots.pyauth.group.GroupNotFound: return jsonify(dict())
def api_groupmember_remove(group_id): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None group_id = html.escape(group_id) # Reject non-JSON payload if not request.json: raise error_handlers.InvalidAPIUsage("bad payload format", status_code=400) request_content = request.get_json() if 'userid' not in request_content: raise error_handlers.InvalidAPIUsage("bad payload", status_code=400) user_id = html.escape(request_content['userid']) try: group = jots.pyauth.group.group(group_id=group_id, db=DB_CON) new_member_list = group.remove_member(user_id=user_id, force=True) except jots.pyauth.group.GroupNotFound: raise error_handlers.InvalidAPIUsage("group not found", status_code=400) except jots.pyauth.group.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except jots.pyauth.group.GroupActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) try: new_member_list = group.get_members_detail(attribute="email") return jsonify(new_member_list) except jots.pyauth.group.GroupActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except jots.pyauth.group.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400)
def api_set_user_attribute(user_id, user_attribute): # Allow the use of a mock DB during testing if app.config['TESTING']: DB_CON = app.config['TEST_DB'] else: DB_CON = None # Reject non-JSON payload if not request.json: raise error_handlers.InvalidAPIUsage("bad payload format", status_code=400) request_content = request.get_json() if "value" not in request_content: raise error_handlers.InvalidAPIUsage("bad payload", status_code=400) user_id = html.escape(user_id) user_attribute = html.escape(user_attribute) attribute_value = html.escape(request_content['value']) try: user = jots.pyauth.user.user(user_id=user_id, db=DB_CON) except jots.pyauth.user.UserNotFound: raise error_handlers.InvalidAPIUsage("invalid user", status=403) except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) # If changing status to reset, we should trigger a password reset # Else use attribute change method if user_attribute == "status" and attribute_value == "reset": try: reset_code = user.reset_password( service_domain=app.config['DOMAIN_NAME']) except jots.pyauth.user.UserActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) # Wmail a link to the 'reset' page with a query string (q) containing reset code try: email_obj = mailer.personalised_email( recipient=user.properties.email, template_name="reset", data={ "site_name": app.config['DOMAIN_NAME'], "reset_url": "https://{}:{}/reset?q={}".format( app.config['DOMAIN_NAME'], app.config['SERVER_PORT'], reset_code) }) if app.config['TESTING']: email_obj.send(mail_agent="file") else: email_obj.send() except mailer.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except mailer.MailActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) return jsonify( {"new_value": user.properties.as_dict()[user_attribute]}) else: try: user.update_named_attribute(user_attribute, attribute_value) return jsonify( {"new_value": user.properties.as_dict()[user_attribute]}) except jots.pyauth.user.InputError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400) except jots.pyauth.user.UserActionError as err: raise error_handlers.InvalidAPIUsage(err.message, status_code=400)