예제 #1
0
    def device_login(self, path=None):
        """
        REDIRECT BROWSER TO AUTH0 LOGIN
        """
        state = request.args.get("state")
        self.session_manager.setup_session(session)
        session.code_verifier = bytes2base64URL(Random.bytes(32))
        code_challenge = bytes2base64URL(
            sha256(session.code_verifier.encode("utf8")))

        query = Data(
            client_id=self.device.auth0.client_id,
            redirect_uri=self.device.auth0.redirect_uri,
            state=state,
            nonce=bytes2base64URL(Random.bytes(32)),
            code_challenge=code_challenge,
            response_type="code",
            code_challenge_method="S256",
            response_mode="query",
            audience=self.device.auth0.audience,
            scope=self.device.auth0.scope,
        )
        url = str(
            URL("https://" + self.device.auth0.domain + "/authorize",
                query=query))

        Log.note("Forward browser to {{url}}", url=url)
        return redirect(url, code=302)
예제 #2
0
 def verify_opaque_token(self, token):
     # Opaque Access Token
     url = "https://" + self.auth0.domain + "/userinfo"
     response = http.get_json(url,
                              headers={"Authorization": "Bearer " + token})
     DEBUG and Log.note("content: {{body|json}}", body=response)
     return response
예제 #3
0
    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)
예제 #4
0
    def clean(self):
        """
        REMOVE ANY RECORDS THAT ARE NOT NEEDED BY QUEUE OR SUBSCRIBERS
        """
        now = Date.now()

        # ANY BLOCKS TO FLUSH?
        with self.db.transaction() as t:
            result = t.query(
                SQL(
                    f"""
                    SELECT id, block_size_mb, block_start
                    FROM {QUEUE} AS q
                    JOIN {BLOCKS} AS b ON b.queue=q.id AND b.serial=q.block_start
                    WHERE b.last_used < {quote_value(now-Duration(WRITE_INTERVAL))}            
                    """
                )
            )

        for stale in rows(result):
            queue = first(q for q in self.queues if q.id == stale.id)
            queue._flush(**stale)

        # REMOVE UNREACHABLE MESSAGES
        conditions = []
        for q in self.queues:
            conditions.append(
                SQL(f"(queue = {quote_value(q.id)} AND serial IN (")
                + SQL(
                    f"""
                    SELECT m.serial
                    FROM {MESSAGES} AS m 
                    LEFT JOIN {UNCONFIRMED} as u ON u.serial = m.serial
                    LEFT JOIN {SUBSCRIBER} as s  ON s.queue = m.queue and s.id = u.subscriber 
                    LEFT JOIN {QUEUE} as q ON q.id = m.queue and m.serial >= q.block_start
                    LEFT JOIN {SUBSCRIBER} as la ON 
                        la.queue = m.queue AND 
                        la.last_confirmed_serial < m.serial AND 
                        m.serial < la.next_emit_serial+la.look_ahead_serial
                    WHERE 
                        m.queue = {q.id} AND
                        s.id IS NULL AND -- STILL UNCONFIRMED POP
                        q.id IS NULL AND -- NOT WRITTEN TO S3 YET
                        la.id IS NULL    -- NOT IN LOOK-AHEAD FOR SUBSCRIBER           
                    """
                )
                + SQL("))")
            )

        with self.db.transaction() as t:
            if DEBUG:
                result = t.query(
                    ConcatSQL(
                        SQL(f"SELECT count(1) AS `count` FROM {MESSAGES} WHERE "),
                        JoinSQL(SQL_OR, conditions),
                    )
                )
                Log.note(
                    "Delete {{num}} messages from database", num=first_row(result).count
                )

            t.execute(
                ConcatSQL(
                    SQL(f"DELETE FROM {MESSAGES} WHERE "), JoinSQL(SQL_OR, conditions)
                )
            )
예제 #5
0
@verify_user
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)


add_flask_rule(APP, "/api/public", public)
add_flask_rule(APP, "/api/private", private)
add_flask_rule(APP, "/api/private-scoped", private_scoped)


config = startup.read_settings()
constants.set(config.constants)
Log.start(config.debug)

session_manager = setup_flask_session(APP, config.session)
perm = Permissions(Sqlite(config.permissions.store))
auth = Authenticator(APP, config.auth0, perm, session_manager)

Log.note("start servers")
setup_flask_ssl(APP, config.flask)
APP.run(**config.flask)