Action plugins: typings (WIP)

Fix Bootstrap when token is expired
This commit is contained in:
phil 2024-03-30 17:56:11 +05:30
parent 393096d0b7
commit 3ca56f22a6
4 changed files with 113 additions and 42 deletions

View file

@ -16,7 +16,8 @@ from gisaf.models.authentication import (
)
from gisaf.models.category import Category, CategoryRead
from gisaf.models.geo_models_base import GeoModel, PlottableModel
from gisaf.models.info import (LegendItem, ModelAction, ModelInfo,
from gisaf.models.info import (ActionParam, ActionResult, ActionResults, ActionsResults, ActionsStore, FormFieldInput, LegendItem,
ModelAction, ModelInfo,
DataProvider, ModelValue, PlotParams,
TagActions)
from gisaf.models.measures import MeasuresItem
@ -51,7 +52,8 @@ api = APIRouter(
@api.get('/bootstrap')
async def bootstrap(
user: Annotated[UserRead, Depends(get_current_active_user)]) -> BootstrapData:
user: Annotated[UserRead, Depends(get_current_active_user)]
) -> BootstrapData:
return BootstrapData(user=user)
@ -100,7 +102,7 @@ async def get_roles(
async def get_acls(db_session: db_session,
user: Annotated[User, Depends(get_current_active_user)]) -> list[UserRoleLink]:
"""New: ACLs returned as UserRoleLink"""
if not user or not user.has_role('manager'):
if user is not None or not user.has_role('manager'):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
data = await db_session.exec(select(UserRoleLink))
return data.all() # type: ignore[return-value]
@ -355,6 +357,7 @@ async def get_model_info(
name=name,
icon=action.icon,
formFields=action.formFields,
roles=action.roles,
)
for name, actions in plugin_manager.actions_stores.get(store, {}).items()
for action in actions
@ -380,4 +383,36 @@ async def get_plot_params(
# *, db_session: AsyncSession = Depends(get_db_session)
# ) -> list[UserRoleLink]:
# roles = await db_session.exec(select(UserRoleLink))
# return roles.all()
# return roles.all()
@api.get('/actions')
async def get_actions() -> list[ActionsStore]:
# actionsPlugins = List(ActionsStore)
return plugin_manager.actionsStores
@api.post('/execTagAction/{action}')
async def execute_tag_action(
user: Annotated[UserRead, Depends(get_current_active_user)],
stores: list[str],
ids: list[list[str]],
actionNames: list[str],
params: list[ActionParam | None],
formFields: list[FormFieldInput],
) -> ActionsResults:
features = dict(zip(stores, [[int(id) for id in _ids] for _ids in ids]))
response = ActionsResults()
#formFields = {field['name']: field['value'] for field in formFields}
if not params:
params = [None] * len(actionNames)
for name in actionNames:
try:
## Give the request from context to execute action, along with the parameters
## FIXME: formFields/names?
breakpoint()
resp = await plugin_manager.execute_action(
user, features, name, params, form_fields=formFields)
response.actionResults.append(resp)
except NoSuchAction:
logger.warn(f'Unknown action {name}')
response.actionResults.append(ActionResult(message=f'No such action: {name}'))
return response

View file

@ -3,15 +3,16 @@ from typing import Any
from pydantic import BaseModel
from gisaf.models.info_item import Tag, InfoItem
from gisaf.models.tags import Tags
class ActionResult(BaseModel):
message: str
# class ActionResult(BaseModel):
# message: str
class ActionResults(BaseModel):
name: str
message: str
actionResults: list[ActionResult]
# class ActionResults(BaseModel):
# name: str
# message: str
# actionResults: list[ActionResult]
class DataProvider(BaseModel):
@ -82,6 +83,7 @@ class FormField(BaseModel):
class ModelAction(BaseModel):
name: str
icon: str
roles: list[str] | None = None
formFields: list[FormField]
@ -160,3 +162,36 @@ class Action(BaseModel):
class ActionsStore(BaseModel):
store: str
actions: list[Action]
class FormFieldInput(BaseModel):
name: str
value: str
class TaggedFeature(BaseModel):
id: str
tags: Tags
lat: float
lon: float
class TaggedLayer(BaseModel):
store: str
taggedFeatures: list[TaggedFeature]
class ActionResult(BaseModel):
message: str | None = None
taggedLayers: list[TaggedLayer] = []
class ActionResults(BaseModel):
name: str | None = None
message: str | None = None
actionResults: list[ActionResult] = []
class ActionsResults(BaseModel):
message: str | None = None
actionResults: list[ActionResults] = []

View file

@ -8,7 +8,7 @@ from datetime import datetime
# from aiohttp.web_exceptions import HTTPUnauthorized
# from aiohttp_security import check_permission
from pydantic import BaseModel # noqa: F401
from fastapi import HTTPException, status
from sqlalchemy import or_, and_
# from geoalchemy2.shape import to_shape, from_shape
# from graphene import ObjectType, String, List, Boolean, Field, Float, InputObjectType
@ -19,17 +19,22 @@ import shapely # type: ignore
from gisaf.config import conf
from gisaf.models.store import Store # noqa: F401
from gisaf.models.tags import Tags as TagsModel
from gisaf.models.authentication import UserRead
from gisaf.utils import upsert_df
from gisaf.models.reconcile import StatusChange
from gisaf.models.info import (
ActionResults,
ActionResult,
ActionsResults,
Downloader,
TagAction, ActionAction,
TagActions,
TagsStore,
TagsStores,
ActionsStore,
Action
Action,
ActionParam, FormFieldInput,
TaggedLayer, TaggedFeature
)
from gisaf.registry import NotInRegistry, registry
@ -57,6 +62,13 @@ class ActionPlugin:
self.icon = icon
self.form_fields = form_fields or []
async def execute(self,
user: UserRead,
features: dict[str, list],
params: list,
form_fields: list[FormFieldInput]) -> ActionResults:
raise NotImplementedError('Action plugins must implement execute')
class TagPlugin:
"""
@ -359,7 +371,13 @@ class PluginManager:
#results.append(await action(features_for_action, key, value))
return ', '.join(results)
async def execute_action(self, request, features, name, params, form_fields):
async def execute_action(self,
user: UserRead,
features: dict,
name: str,
params: list[ActionParam | None],
form_fields: list[FormFieldInput]
) -> ActionsResults:
"""
Execute the plugin action by calling the executor's execute function.
It is up to the plugin action to check for security, using eg:
@ -368,42 +386,22 @@ class PluginManager:
...
await check_permission(request, 'role')
"""
results = []
results = ActionsResults()
try:
plugins = self.actions_names[name]
except KeyError:
raise NoSuchAction
for executor in self.executors[name]:
## TODO: get features from DB?
result: ActionResults
## Check permission
if executor.roles:
authorized = False
for role in executor.roles:
try:
await check_permission(request, role)
except HTTPUnauthorized as err:
pass
else:
authorized = True
break
else:
## No roles: OK for anonymous
authorized = True
if authorized:
result = await executor.execute(
request, features, params,
**{field['name']: field for field in form_fields}
)
result.name = name
results.append(result)
else:
raise HTTPUnauthorized
return ActionsResults(
actionResults=results
)
if user is None or not any([user.has_role(role) for role in executor.roles]):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
result = await executor.execute(user, features, params, form_fields)
result.name = name
results.actionResults.append(result)
return results
#for store, ids in all_features.items():
# actions = self.actions_stores[store]

View file

@ -115,7 +115,10 @@ async def get_current_user(
if username == '':
raise credentials_exception
except ExpiredSignatureError:
raise expired_exception
# raise expired_exception
decoded = jwt.get_unverified_claims(token)
logger.debug(f"Session expired for user {decoded.get('sub')}")
return None
except JWTError:
raise credentials_exception
async with db_session() as session: