async def create_with_relations(request: Request, items: Union[Dict, List[Dict]]): # NOTES # How do I check each child relations permissions, there is no includes # Would have to flip each item key and check if its a relation? # Then check if user has access to that relation? # Insert into storage and return primary key inserted try: # Ensure list is_single = False if type(items) != list: items = [items] is_single = True for item in items: pk = await Model.insert_with_relations(item.copy()) # If primary key is read_only, assume its an auto-incrementing pk # If not read_only, its a manual pk like 'key'. # Only set new pk result if pk is read_only. Why? Because when pk is 'key' # a string, encode/databases does not return the new pk, just returns 1 every time. if Model.modelfield(Model.pk).read_only == True: setvalue(item, Model.pk, pk) # Return inserted items #dump(item) if is_single: return items[0] else: return items except Exception as e: raise HTTPException(500, str(e))
async def get_all(**kwargs): api = AutoApi(Model, scopes, **kwargs).guard_relations() query = api.orm_query() # Run ORM query for results try: results = await query.get() return results except Exception as ex: dump(ex) raise HTTPException(500, str("Error in query builder, most likely an unknown column or query parameter."))
async def get_one(id: Union[str,int], **kwargs): api = AutoApi(Model, scopes, **kwargs).guard_relations() query = api.orm_query() # Run ORM query for results try: results = await query.find(id) if results: return results else: return JSONResponse(status_code=404, content={"message": "Item not found"}) except: raise HTTPException(500, str("Error in query builder, most likely an unknown column or query parameter."))
async def delete_one(request: Request, id: Union[str,int]): # DELETE must act on a single resource ONLY /{id}. It does NOT take a body/paylad at all. # If you want to BULK delete using a body/payload with a JSON query, use POST instead with # a new custom endpoint like POST /users/delete payload={"where": ...} try: result = await Model.query().find(id) except Exception as e: raise HTTPException(500, str(e)) if result: #await result.unlink('tags') await result.delete() return result else: raise NotFound('asdf')
async def __call__(self, security_scopes: SecurityScopes, request: Request) -> User: # Get scopes List. These are permissions/scopes defined on this route scopes = list(security_scopes.scopes) #dump('ROUTE SCOPES:', scopes) # Get user from request. Will always exist. If not logged it will be # the anonymous user which may still have permissions/scopes to compare user: User = request.user #dump('USER PERMISSIONS', user.permissions) # If no scopes, allow access if not scopes: return user # Superadmin always wins, never check scopes if user.superadmin: return user # User must have ALL scopes defined on the route (an AND statement) authorized = True missing_scopes = [] for scope in scopes: if scope not in user.permissions: authorized = False missing_scopes.append(scope) #dump('-----------------------------------------------------------------') #dump('Auth Route Guard HERE:', scopes, user) #dump('-----------------------------------------------------------------') # Hack logout of basic auth #raise NotAuthenticated(headers={'WWW-Authenticate': 'Basic realm="App1 Web Realm"'}) # Authorized and authenticated. Return user in case Guard() is being used # as a route parameter using FastAPI Depends. User will be injected back to param value. if authorized: return user # Not authorized or possibly even authenticated # Get unauthenticated handler from auth config # Route type (web or api) is added to request.scope from authentication middleware route_type = request.scope.get('route_type') or 'api' # Get the route auth config based on route_type auth_config = uvicore.config.app.auth[route_type] # Default unauthenticated is to raise exception. # Check for redirect override or additional exception headers if auth_config.unauthenticated_handler.redirect: location = auth_config.unauthenticated_handler.redirect if '/' not in location and '.' in location: location = request.url_for(location) separator = '&' if '?' in location else '?' referer = separator + "referer=" + str(request.scope.get('path')) raise HTTPException(status_code=301, headers={ 'Location': location + referer }) # Check for additional exception headers exception_headers = None if auth_config.unauthenticated_handler.exception: exception_headers = auth_config.unauthenticated_handler.exception.headers if user.authenticated: # User authenticated but does not have all route scopes raise PermissionDenied(missing_scopes, headers=exception_headers) else: # User is not even logged in raise NotAuthenticated(headers=exception_headers)
async def __call__(self, scopes: SecurityScopes, request: Request): dump('JWT HERE') dump(scopes.__dict__, self.config) dump('-----------------------------------------------------------------') # Assume unauthorized authorized = False # Parse authorization header authorization, scheme, token = self.auth_header(request) #dump("AUTHORIZATION:", authorization) #dump("SCHEME", scheme) #dump("TOKEN", token) dump('HEADERS', request.headers) # Detect anonymous API access. # API gateways that allow anonymous access to an API will pass the request # downstream even if the token is missing, invalid or expired. # When they do, they add a header notifying downstream that auth has failed # but anonymous access is still allowed. For Kong this header is x-anonymous-consumer # If the API gateway does not allow anonymous access, downstream won't even be proxied. if (self.config.api_gateway.enabled and self.config.api_gateway.anonymous_header and self.config.api_gateway.anonymous_header in request.headers ): # Anonymous header configured and found in headers. This is an anonymous connection. # No need to check JWT, simply return anonymous "public" user. dump('ANONYMOUS CONNECTION, RETURN PUBLIC USER') return # Kong with NO JWT using anonymous example # headers """ {'host': 'sunjaro:5000', 'connection': 'keep-alive', 'x-forwarded-for': '127.0.0.1, 172.26.0.1', 'x-forwarded-proto': 'https', 'x-forwarded-host': 'uvicore-local.sunfinity.io', 'x-forwarded-port': '8443', 'x-real-ip': '172.26.0.1', 'user-agent': 'HTTPie/2.4.0', 'accept-encoding': 'gzip, deflate', 'accept': '*/*', # This is anonymous kong ID if x-anonymous-consumer = True 'x-consumer-id': '1be13da8-296d-4653-bb68-cdc7f4b54a6d', 'x-consumer-custom-id': 'anonymous', 'x-consumer-username': '******', 'x-anonymous-consumer': 'true'}) """ # Headers, Kong with valid token """ {'host': 'sunjaro:5000', 'connection': 'keep-alive', 'x-forwarded-for': '127.0.0.1, 172.26.0.1', 'x-forwarded-proto': 'https', 'x-forwarded-host': 'uvicore-local.sunfinity.io', 'x-forwarded-port': '8443', 'x-real-ip': '172.26.0.1', 'user-agent': 'HTTPie/2.4.0', 'accept-encoding': 'gzip, deflate', 'accept': '*/*', 'authorization': 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ii1GV1Z1eEg0eGh2NnhxQUV5akVfdXBfd0NiZyJ9.eyJhdWQiOiJkNzA5YTQzMi01ZWRjLTQ0YzctODcyMS00Y2YxMjM0NzNjNDUiLCJleHAiOjE2MTUzMzE0OTAsImlhdCI6MTYxNTMyNzg5MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLWxvY2FsLnN1bmZpbml0eS5jb20iLCJzdWIiOiIxNGEzZjY0Yi0zNzcwLTQ1NDktOWZjNS00YjYxZmY4YjIwNzAiLCJhdXRoZW50aWNhdGlvblR5cGUiOiJQQVNTV09SRCIsImVtYWlsIjoibWFuYWdlcjFAZXhhbXBsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXBwbGljYXRpb25JZCI6ImQ3MDlhNDMyLTVlZGMtNDRjNy04NzIxLTRjZjEyMzQ3M2M0NSIsInJvbGVzIjpbIkVtcGxveWVlIiwiU2FsZXMgSW50ZXJuYWwiLCJVc2VyIl0sIm5hbWUiOiJNYW5hZ2VyfE9uZSJ9.JaR8EdMdd5XdUBZuPWXm-uVghODp84LzQON3kwwshqrjR5PyAO6HP6S5wGg1Y-dKMfETkbNy5sh9RP3nZWHtwB7ZukQ3IwIrC5Ako3a1VZWFzSu1mR3_1X510DvTQVwdfWz_XvEwG5v8O01n-UrLXWQHvgKDeEs3HPkuel-kHXajhoP6oEVxXqb3AmzCIoOv9MC3_3F4jVW73FLFQA-thsVElpJiwrLlZ9ehulp-Cl92eCOS6Ug-L6aJdafx7X9Q2gTsY28cN85WVCX3dss3gJ1GO8vq8u8yIek4B-27hRtyVwaGlxKWjGPsLjE-M0VvXSqQrCIszvxlcodo1L1F5A', # This is kongs consumer GUID 'x-consumer-id': '304ae0c3-de56-47dc-8c35-7c8ed7f6c08f', # This is the uvicore app ID 'x-consumer-custom-id': 'd709a432-5edc-44c7-8721-4cf123473c45', 'x-consumer-username': '******', 'x-credential-identifier': 'd709a432-5edc-44c7-8721-4cf123473c45'}) """ # This authorization method not provided or attempted, goto next guard in middleware stack if not authorization or scheme != "bearer": # Return None means goto next authenticator in authorization middleware return None # Decode JWT try: if self.config.verify_signature: # With secret algorithm validation dump('WITH Validation') payload = Dict(jwt.decode(token, self.config.secret, audience=self.config.audience, algorithms=self.config.algorithms)) else: # Without validation dump('WITHOUT Validation') payload = Dict(jwt.decode(token, options={"verify_signature": False})) # Validate aud claim # jwt.decode also validates the aud claim. Since we are skipping validation, we'll still validate aud claim here. if payload.aud != self.config.audience: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid audience", ) except Exception as e: # Issue with validation. Bad key, token expired... # Pass JWT library exception message right through with a generic 401 raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e), ) dump('JWT', payload) # { # 'aud': '222b06eb-85ce-472b-af30-ec09244e3bf0', # 'exp': 1614980706, # 'iat': 1614977106, # 'iss': 'https://auth-local.sunfinity.com', # 'sub': '217e27b4-0e0a-464c-84a8-c40312b55801', # 'authenticationType': 'PASSWORD', # 'email': '*****@*****.**', # 'email_verified': True, # 'applicationId': '222b06eb-85ce-472b-af30-ec09244e3bf0', # 'roles': ['Administrator'], # 'name': 'Admin|Istrator' # } # Get user and validate credentials user = await self.retrieve_user(payload.email, None, self.config.provider) # If user is none and auto_create_user is enabled, auto-create user # Link user up to groups table based on JWT roles # Now self.get_user again # If no user returned, validation has failed or user not found if user is None: raise InvalidCredentials() # Validate Permissions #self.validate_permissions(user, scopes.scopes) # Authorization successful. # Add user to request in case we use it in a decorator, we can pull it out with request.scope.get('user') request.scope['user'] = user # Return user in case we are using this guard as a dependency injected route parameter return user