def login(self, path=None): """ EXPECT AN ACCESS TOKEN, RETURN A SESSION TOKEN """ now = Date.now().unix try: access_token = get_token_auth_header() # if access_token.error: # Log.error("{{error}}: {{error_description}}", access_token) if len(access_token.split(".")) == 3: access_details = self.verify_jwt_token(access_token) session.scope = access_details["scope"] # ADD TO SESSION self.session_manager.setup_session(session) user_details = self.verify_opaque_token(access_token) session.user = self.permissions.get_or_create_user(user_details) session.last_used = now self.markup_user() return Response(value2json( self.session_manager.make_cookie(session)), status=200) except Exception as e: session.user = None session.last_used = None Log.error("failure to authorize", cause=e)
def verify_jwt_token(self, token): jwks = http.get_json("https://" + self.auth0.domain + "/.well-known/jwks.json") unverified_header = jwt.get_unverified_header(token) algorithm = unverified_header["alg"] if algorithm != "RS256": Log.error("Expecting a RS256 signed JWT Access Token") key_id = unverified_header["kid"] key = unwrap(first(key for key in jwks["keys"] if key["kid"] == key_id)) if not key: Log.error("could not find {{key}}", key=key_id) try: return jwt.decode( token, key, algorithms=algorithm, audience=self.auth0.api.identifier, issuer="https://" + self.auth0.domain + "/", ) except jwt.ExpiredSignatureError as e: Log.error("Token has expired", code=403, cause=e) except jwt.JWTClaimsError as e: Log.error( "Incorrect claims, please check the audience and issuer", code=403, cause=e, ) except Exception as e: Log.error("Problem parsing", cause=e)
def get_subscriber(self, name=None): """ GET SUBSCRIBER BY id, OR BY QUEUE name """ with self.db.transaction() as t: result = t.query( SQL( f""" SELECT MIN(s.id) as id FROM {SUBSCRIBER} AS s LEFT JOIN {QUEUE} as q on q.id = s.queue WHERE q.name = {quote_value(name)} GROUP BY s.queue """ ) ) if not result: Log.error("not expected") queue = self.get_or_create_queue(name) sub_info = t.query( sql_query( {"from": SUBSCRIBER, "where": {"eq": {"id": first_row(result).id}}} ) ) return Subscription(queue=queue, kwargs=first_row(sub_info))
def output(*args, **kwargs): # IS THIS A NEW SESSION now = Date.now().unix user = session.get("user") if not user: Log.error("must authorize first") session.last_used = now return func(*args, user=user, **kwargs)
def get_token_auth_header(): """Obtains the Access Token from the Authorization Header """ try: auth = request.headers.get("Authorization", None) bearer, token = auth.split() if bearer.lower() == "bearer": return token except Exception as e: pass Log.error('Expecting "Authorization = Bearer <token>" in header')
def private_scoped(user): """A valid access token and an appropriate scope are required to access this route """ if requires_scope(config.auth0.scope): response = ( "Hello from a private endpoint! You need to be authenticated and have a scope of " + config.auth0.scope + " to see this." ) return jsonify(message=response) Log.error("You don't have access to {{scope}}", scope=config.auth0.scope, code=403)
def annotation(): if flask.request.headers.get("content-length", "") in ["", "0"]: # ASSUME A BROWSER HIT THIS POINT, SEND text/html RESPONSE BACK return Response(ERROR_CONTENT, status=400, headers={"Content-Type": "text/html"}) elif int(flask.request.headers["content-length"]) > QUERY_SIZE_LIMIT: Log.error("Query is too large to parse") request_body = flask.request.get_data().strip() text = utf82unicode(request_body) data = json2value(text) try: record_request(flask.request, data, None, None) except Exception as e: Log.error("Problem processing request {{request}}")
def __init__(self, flask_app, auth0, permissions, session_manager, device=None): if not auth0.domain: Log.error("expecting auth0 configuration") self.auth0 = auth0 self.permissions = permissions self.session_manager = session_manager # ATTACH ENDPOINTS TO FLASK APP endpoints = auth0.endpoints if not endpoints.login or not endpoints.logout or not endpoints.keep_alive: Log.error("Expecting paths for login, logout and keep_alive") add_flask_rule(flask_app, endpoints.login, self.login) add_flask_rule(flask_app, endpoints.logout, self.logout) add_flask_rule(flask_app, endpoints.keep_alive, self.keep_alive) if device: self.device = device db = self.device.db = Sqlite(device.db) if not db.about("device"): with db.transaction() as t: t.execute( sql_create( "device", { "state": "TEXT PRIMARY KEY", "session_id": "TEXT" }, )) if device.auth0.redirect_uri != text_type( URL(device.home, path=device.endpoints.callback)): Log.error( "expecting home+endpoints.callback == auth0.redirect_uri") add_flask_rule(flask_app, device.endpoints.register, self.device_register) add_flask_rule(flask_app, device.endpoints.status, self.device_status) add_flask_rule(flask_app, device.endpoints.login, self.device_login) add_flask_rule(flask_app, device.endpoints.callback, self.device_callback)
def logout(self, path=None): if not session.session_id: Log.error("Expecting a sesison token") session.user = None session.last_used = None return Response(status=200)
def keep_alive(self, path=None): if not session.session_id: Log.error("Expecting a sesison token") now = Date.now().unix session.last_used = now return Response(status=200)
def device_callback(self, path=None): # HANDLE BROWESR RETURN FROM AUTH0 LOGIN error = request.args.get("error") if error: Log.error("You did it wrong") code = request.args.get("code") state = request.args.get("state") referer = request.headers.get("Referer") result = self.device.db.query( sql_query({ "from": "device", "select": "session_id", "where": { "eq": { "state": state } }, })) if not result.data: Log.error("expecting valid state") device_session_id = result.data[0][0] # GO BACK TO AUTH0 TO GET TOKENS token_request = { "client_id": self.device.auth0.client_id, "redirect_uri": self.device.auth0.redirect_uri, "code_verifier": session.code_verifier, "code": code, "grant_type": "authorization_code", } DEBUG and Log.note("Send token request to Auth0:\n {{request}}", request=token_request) auth_response = requests.request( "POST", str(URL("https://" + self.device.auth0.domain, path="oauth/token")), headers={ "Accept": "application/json", "Content-Type": "application/json", # "Referer": str(URL(self.device.auth0.redirect_uri, query={"code": code, "state": state})), }, data=value2json(token_request), ) try: auth_result = wrap(auth_response.json()) except Exception as e: Log.error("not json {{value}}", value=auth_response.content, cause=e) # VERIFY TOKENS, ADD USER TO DEVICE'S SESSION user_details = self.verify_opaque_token(auth_result.access_token) self.session_manager.update_session( device_session_id, {"user": self.permissions.get_or_create_user(user_details)}, ) # REMOVE DEVICE SETUP STATE with self.device.db.transaction() as t: t.execute(SQL_DELETE + SQL_FROM + quote_column(self.device.table) + SQL_WHERE + sql_eq(state=state)) Log.note("login complete") return Response("Login complete. You may close this page", status=200)
def delete_queue(self, name): Log.error("You do not need to do this")