async def get_long_lived_token( request: web.Request) -> JSONResponse: """ Generates a long-lived access token This token will be valid for 10 years """ try: data = vol.Schema({ vol.Required("client_id"): str, vol.Optional( "client_name", default=None): vol.Any(str, None) })(await request.json()) except (vol.Invalid, JSONDecodeError) as e: return JSONResponse(error=str(e)) refresh_token = await self.auth_manager.create_refresh_token( client_id=data["client_id"], user=request.user, access_token_expiration=timedelta(days=3650), client_name=data["client_name"] ) access_token = await self.auth_manager.create_access_token( refresh_token) return JSONResponse({ "access_token": access_token.token, "token_type": "bearer", "expires_in": access_token.expiration, "refresh_token": refresh_token.token, }, headers={ "Cache-Control": "no-store" })
async def post(self) -> JSONResponse: """POST /bind_credentials""" try: data = vol.Schema({ vol.Required("provider", default="password"): vol.Any(*self.auth_manager.credential_providers.keys()), vol.Optional("user", default=None): vol.Any(str, None), vol.Optional("data", default=None): object })(await self.request.json()) except (vol.Invalid, JSONDecodeError) as e: return JSONResponse(error=str(e)) if data["user"] and not self.request["user"].owner: return JSONResponse(error="Unauthorized", status_code=401) user = self.auth_manager.get_user(data["user"]) or self.request["user"] provider: "CredentialProvider" = ( self.auth_manager.credential_providers[data["provider"]]) creds, return_data = await provider.create_credentials( user, data["data"]) self.auth_manager.users.schedule_save() return JSONResponse({ "credential_id": creds.credential_id, "data": return_data })
async def get_item_state(request: web.Request) -> JSONResponse: identifier = request.match_info["id"] state_name = request.match_info["state_name"] item = self.core.item_manager.items.get(identifier) if not item: return JSONResponse(error={ "type": ERROR_ITEM_NOT_FOUND, "message": f"No item found with identifier {identifier}" }, status_code=404) if state_name not in item.states.states: return JSONResponse( error={ "type": ITEM_STATE_NOT_FOUND, "message": f"Couldn't get state {state_name}" f"from item {identifier}" }) return JSONResponse({ "item": item, "type": item.type, "states": { state_name: await item.states.get(state_name) } })
async def post(self) -> JSONResponse: """POST /item/{id}/states""" identifier = self.data["id"] item = self.core.item_manager.items.get(identifier) if not item: return self.error( ERROR_ITEM_NOT_FOUND, f"No item found with identifier {identifier}", 404) try: content = (await self.request.content.read()).decode() commit = STATE_COMMIT_SCHEMA( json.loads(content) if content else {}) # pylint: disable=broad-except except Exception as e: return JSONResponse(error=e) if not commit.keys() & item.states.states.keys() == commit.keys(): return self.error( ERROR_INVALID_ITEM_STATES, f"The given states {set(commit.keys())} do not comply with " f"accepted states {set(item.states.states.keys())}") if not item.status == ItemStatus.ONLINE: return JSONResponse( error=ItemNotOnlineError( f"The item {item.identifier} is not online")) return JSONResponse(dict(ChainMap( *[await item.states.set(state, value) for state, value in commit.items()])))
async def get_actions(request: web.Request) -> JSONResponse: identifier = request.match_info["id"] item = self.core.item_manager.items.get(identifier) if not item: return JSONResponse(error={ "type": ERROR_ITEM_NOT_FOUND, "message": f"No item found with identifier {identifier}" }, status_code=404) return JSONResponse(data=list(item.actions.actions.keys()))
async def do_login_flow_step(request: web.Request) -> JSONResponse: """Executes the current login_flow step""" try: data = await request.json() except JSONDecodeError as e: return JSONResponse(error=str(e)) flow = self.flow_manager.flows.get(request.match_info["flow_id"]) if not flow: return JSONResponse(error="Login flow does not exist") step = flow.get_step(flow.current_step) step_result = await step(data) return JSONResponse(step_result.to_json())
async def post(self) -> JSONResponse: """POST /long_lived_token""" try: data = vol.Schema({ vol.Required("client_id"): str, vol.Optional("client_name", default=None): vol.Any(str, None) })(await self.request.json()) except (vol.Invalid, JSONDecodeError) as e: return self.error(e) refresh_token = await self.auth_manager.create_refresh_token( client_id=data["client_id"], user=self.request["user"], access_token_expiration=timedelta(days=3650), client_name=data["client_name"]) access_token = await self.auth_manager.create_access_token( refresh_token) return JSONResponse( { "access_token": access_token.token, "token_type": "bearer", "expires_in": access_token.expiration, "refresh_token": refresh_token.token, }, headers={"Cache-Control": "no-store"})
async def execute_action(request: web.Request) -> JSONResponse: """ Executes an item's action and returns a boolean indicating the success of the action. Changed states or other events can be handled through the websocket endpoint /events """ identifier = request.match_info["id"] action_name = request.match_info["action_name"] item = self.core.item_manager.items.get(identifier) if not item: return JSONResponse(error={ "type": ERROR_ITEM_NOT_FOUND, "message": f"No item found with identifier {identifier}" }, status_code=404) try: content = (await request.content.read()).decode() kwargs = json.loads(content) if content else {} except JSONDecodeError as e: return JSONResponse(error=e) if action_name not in item.actions.actions: return JSONResponse( error={ "type": ITEM_ACTION_NOT_FOUND, "message": f"Item {identifier} of type {item.type} " f"does not have an action {action_name}" }) try: return JSONResponse({ "item": item, "action": action_name, "result": await item.actions.execute(action_name, **kwargs) }) # pylint: disable=broad-except except Exception as e: return JSONResponse(error=e)
async def get_event_log_by_type(request: web.Request) -> JSONResponse: event_type = request.match_info["type"] with self.session_context() as session: result = session.query(models.EventLog).filter( models.EventLog.event_type == event_type) return JSONResponse(result.all())
async def get_item_states(request: web.Request) -> JSONResponse: identifier = request.match_info["id"] item = self.core.item_manager.items.get(identifier) if not item: return JSONResponse(error={ "type": ERROR_ITEM_NOT_FOUND, "message": f"No item found with identifier {identifier}" }, status_code=404) return JSONResponse({ "item": item, "type": item.type, "states": await item.states.dump(), })
def json(self, data: Any = None, status_code: int = 200, headers: dict = None) -> JSONResponse: """Creates a JSONResponse""" return JSONResponse(data, status_code=status_code, core=self.core, headers=headers)
async def reload_item(request: web.Request) -> JSONResponse: identifier = request.match_info["id"] item = self.core.item_manager.items.get(identifier) if not item: return JSONResponse(error={ "type": ERROR_ITEM_NOT_FOUND, "message": f"No item found with identifier {identifier}" }, status_code=404) await self.core.item_manager.recreate_item(item) return JSONResponse({ "message": f"Item {identifier} recreated", "status": item.status.value })
async def get_items(request: web.Request) -> JSONResponse: return JSONResponse([{ "id": item.identifier, "name": item.name, "type": item.type, "module": item.module.name, "status": item.status.value, "actions": list(item.actions.actions.keys()), "states": await item.states.dump() } for item in self.core.item_manager.items.values()])
async def set_item_states(request: web.Request) -> JSONResponse: identifier = request.match_info["id"] item = self.core.item_manager.items.get(identifier) if not item: return JSONResponse(error={ "type": ERROR_ITEM_NOT_FOUND, "message": f"No item found with identifier {identifier}" }, status_code=404) try: content = (await request.content.read()).decode() commit = STATE_COMMIT_SCHEMA( json.loads(content) if content else {}) # pylint: disable=broad-except except Exception as e: return JSONResponse(error=e) if not commit.keys() & item.states.states.keys() == commit.keys(): return JSONResponse( error={ "type": ERROR_INVALID_ITEM_STATES, "message": f"The given states {set(commit.keys())} do not" f"comply with accepted states " f"{set(item.states.states.keys())}" }) if not item.status == ItemStatus.ONLINE: return JSONResponse(error=ItemNotOnlineError( f"The item {item.identifier} is not online")) return JSONResponse( dict( ChainMap(*[ await item.states.set(state, value) for state, value in commit.items() ])))
async def create_login_flow(request: web.Request) -> JSONResponse: """Creates a login flow""" try: data = vol.Schema({ vol.Required("provider"): vol.Any( *self.flow_manager.flow_factories.keys()), vol.Required("client_id"): str })(await request.json()) except (vol.Invalid, JSONDecodeError) as e: return JSONResponse(error=str(e)) flow = await self.flow_manager.create_flow( data["provider"], data["client_id"]) if not flow: return JSONResponse(error="Login flow does not exist") first_result = await flow.step_init(data) return JSONResponse(first_result.to_json())
async def get_item(request: web.Request) -> JSONResponse: identifier = request.match_info["id"] item = self.core.item_manager.items.get(identifier) if not item: return JSONResponse(error={ "type": ERROR_ITEM_NOT_FOUND, "message": f"No item found with identifier {identifier}" }, status_code=404) return JSONResponse({ "id": item.identifier, "type": item.type, "module": item.module.name, "config": item.cfg, "status": item.status.value })
async def get_state_log(request: web.Request) -> JSONResponse: identifier = request.match_info["item"] state_name = request.query.get("state", None) with self.session_context() as session: result = session.query(models.StateLog).filter( models.StateLog.item_identifier == identifier) if state_name: result = result.filter( models.StateLog.state_name == state_name) return JSONResponse(result.all())
def error(self, error: Union[str, Exception], message: str = None, status_code: int = 500) -> JSONResponse: """Creates an error message""" return JSONResponse(error={ "type": type(error).__name__, "message": str(error) } if isinstance(error, Exception) else { "type": error, "message": message }, status_code=status_code)
async def set_item_state(request: web.Request) -> JSONResponse: identifier = request.match_info["id"] item = self.core.item_manager.items.get(identifier) if not item: return JSONResponse(error={ "type": ERROR_ITEM_NOT_FOUND, "message": f"No item found with identifier {identifier}" }, status_code=404) state_name = request.match_info["state_name"] if state_name not in item.states.states.keys(): return JSONResponse( error={ "type": ERROR_INVALID_ITEM_STATE, "message": f"The given state '{state_name}' does not exist" }) if not item.status == ItemStatus.ONLINE: return JSONResponse(error=ItemNotOnlineError( f"The item {item.identifier} is not online")) try: content = (await request.content.read()).decode() value = json.loads(content) if content else {} result = await item.states.set(state_name, value) # pylint: disable=broad-except except (Exception, vol.error.Error) as e: return JSONResponse(error=e) return JSONResponse(result)
async def create_user(request: web.Request) -> JSONResponse: """Creates a user""" payload_schema = vol.Schema({ vol.Required("name"): str, vol.Required("password"): str, vol.Optional("is_owner", default=False): bool, }) payload = payload_schema(await request.json()) user = await self.auth_manager.create_user( name=payload["name"], owner=payload["is_owner"] ) provider: CredentialProvider = ( self.auth_manager.credential_providers["password"]) await provider.create_credentials(user, payload["password"]) self.auth_manager.users.schedule_save() return JSONResponse({ "user_id": user.id })
async def ping(request: web.Request) -> JSONResponse: """Handle /ping""" return JSONResponse("PONG")
async def post(self) -> JSONResponse: """POST /token""" try: data = dict((await self.request.post()).items()) payload = TOKEN_PAYLOAD_SCHEMA(data) except vol.Invalid as e: path = ', '.join(str(var) for var in e.path) return self.json( { "error": "invalid_request", "error_description": f"Invalid parameters: {path}" }, status_code=400) if payload["grant_type"] == "code": try: payload = AUTH_CODE_SCHEMA(payload) except vol.Invalid: return self.json( { "error": "invalid_grant", "error_description": "Invalid authorization code" }, status_code=400) code = self.auth_manager.auth_codes.get(payload["code"]) if not code: return self.json( { "error": "invalid_grant", "error_description": "Invalid authorization code" }, status_code=400) if code.expired: return self.json( { "error": "invalid_grant", "error_description": "Authorization code expired" }, status_code=400) self.auth_manager.remove_authorization_code(code) refresh_token = await self.auth_manager.create_refresh_token( client_id=cast(str, code.client_id), access_token_expiration=code.access_token_expiration, user=code.user) elif payload["grant_type"] == "password": try: payload = PASSWORD_SCHEMA(payload) except vol.Invalid: return self.json( { "error": "invalid_grant", "error_description": "Invalid credentials" }, status_code=400) cred_provider: "CredentialProvider" = ( self.auth_manager.credential_providers["password"]) user = self.auth_manager.get_user_by_name(payload["username"]) login_valid = await cred_provider.validate_login_data( user, payload["password"]) if user else False if not login_valid: return self.json( { "error": "invalid_grant", "error_description": "Invalid credentials" }, status_code=400) refresh_token = await self.auth_manager.create_refresh_token( payload["client_id"], user=user) elif payload["grant_type"] == "refresh_token": try: payload = REFRESH_TOKEN_SCHEMA(payload) except vol.Invalid as e: if "refresh_token" in e.path: return self.json( { "error": "invalid_grant", "error_description": "Grant type 'refresh_token'" "needs a 'refresh_token'" "parameter" }, status_code=400) invalid = ', '.join(e.path) return self.json( { "error": "invalid_request", "error_description": f"Invalid parameters: {invalid}" }, status_code=400) refresh_token = self.auth_manager.get_refresh_token_by_string( payload["refresh_token"]) if not refresh_token: return self.json( { "error": "invalid_grant", "error_description": "Invalid refresh token" }, status_code=400) else: return self.json({ "error": "invalid_grant", "error_description": "The given grant type is not supported " }) access_token = await self.auth_manager.create_access_token( refresh_token) return JSONResponse( { "access_token": access_token.token, "token_type": "bearer", "expires_in": access_token.expiration, "refresh_token": refresh_token.token, }, headers={hdrs.CACHE_CONTROL: "no-store"})
async def get_event_log(request: web.Request) -> JSONResponse: with self.session_context() as session: result = session.query(models.EventLog) return JSONResponse(result.all())
async def shutdown(request: web.Request) -> JSONResponse: """Handle /core/shutdown""" self.core.loop.call_soon(self.core.shutdown) return JSONResponse("Shutting down")
async def get_login_flow_providers( request: web.Request) -> JSONResponse: """Returns a list of login providers""" return JSONResponse(list(self.flow_manager.flow_factories.keys()))
async def restart(request: web.Request) -> JSONResponse: self.core.loop.call_soon(self.core.restart) return JSONResponse("Restarting")
async def reload_config(request: web.Request) -> JSONResponse: await self.core.cfg.reload_config() return JSONResponse("Reloaded configuration")