async def authenticate(request, *args, **kwargs): """ - """ del args, kwargs msg = "Error -> Auth -> authenticate" with ax_model.scoped_session(msg) as db_session: email = request.json.get("email", None) password = request.json.get("password", None) if not email or not password: raise exceptions.AuthenticationFailed("Missing email or password.") user = db_session.query(AxUser).filter( AxUser.email == email ).filter( AxUser.is_blocked.is_(False) ).first() if user is None: raise exceptions.AuthenticationFailed("User not found.") if not pbkdf2_sha256.verify(password, user.password): raise exceptions.AuthenticationFailed("Password is incorrect.") await check_if_admin(user_guid=str(user.guid), db_session=db_session) await write_perm_cache(db_session=db_session, user_guid=str(user.guid)) await write_info_cache(user) db_session.expunge(user) return user
def sync_field_types(): """ Read field_types.yaml and check if any fields need to be created or modified """ field_types_yaml = ax_misc.path('field_types.yaml') with ax_model.scoped_session() as db_session: ax_field_types = db_session.query(AxFieldType).all() existing_tags = [] for field_type in ax_field_types: existing_tags.append(field_type.tag) if not os.path.isfile(field_types_yaml): raise FileNotFoundError( 'Configuration failed, field_types.yaml not found') with open(field_types_yaml, 'r') as stream: yaml_vars = yaml.safe_load(stream) for item in yaml_vars: if isinstance(item, dict) and 'tag' in item: tag = item['tag'] if tag in existing_tags: update_field_type(field_types=ax_field_types, item=item) else: create_field_type(db_session=db_session, item=item) db_session.commit()
def collect_ax_stats(): """ Collect Ax usage statistics """ with ax_model.scoped_session() as db_session: values = {} values['host'] = os.environ.get('AX_HOST', None) values['url'] = os.environ.get('AX_URL', None) values['dialect'] = os.environ.get('AX_DB_DIALECT', None) values['cache'] = os.environ.get('AX_CACHE_MODE', None) values['workersNum'] = os.environ.get('AX_SANIC_WORKERS', None) values['fingerprint'] = uuid.getnode() values['clientGuid'] = ax_auth.client_guid values['axVersion'] = os.environ.get('AX_VERSION', None) values['usersNum'] = db_session.query(AxUser).filter( AxUser.is_group.is_(False)).count() values['formsNum'] = db_session.query(AxForm).filter( AxForm.is_folder.is_(False)).count() # dbSize - #TODO how to do it? # sslUsed if os.environ.get('SSL_CERT_ABSOLUTE_PATH', None): values['sslUsed'] = True # actionCodeUsed if db_session.query(AxAction).filter( AxAction.code.isnot(None)).first(): values['actionCodeUsed'] = True # gridCodeUsed if db_session.query(AxGrid).filter(AxGrid.code.isnot(None)).first(): values['gridCodeUsed'] = True # roleCodeUsed if db_session.query(AxRole).filter(AxRole.code.isnot(None)).first(): values['roleCodeUsed'] = True # adminEmails admin_emails = [] admin_group = db_session.query(AxUser).filter( AxUser.is_admin.is_(True)).filter( AxUser.is_group.is_(True)).first() if admin_group: for usr in admin_group.users: if usr.email != '*****@*****.**': admin_emails.append(usr.email) values['adminEmails'] = ", ".join(admin_emails) # fieldUsage # ax_fields = db_session.query(AxField.field_type_tag).filter( # AxField.is_tab.is_(False) # ).all() # field_dict = {} # for fld in ax_fields: # if fld.field_type_tag not in field_dict: # field_dict[fld.field_type_tag] = 1 # else: # field_dict[fld.field_type_tag] += 1 # values['fieldUsage'] = field_dict this.stats = values
def database_fits_metadata() -> None: """Compare yaml db version and version in actual database""" with ax_model.scoped_session() as db_session: alembic_version = db_session.query(AxAlembicVersion).first() version = alembic_version.version_num yaml_version = os.environ.get('AX_DB_REVISION') if yaml_version == version: return True return False
async def db_file_viewer( # pylint: disable=unused-variable request, form_guid, row_guid, field_guid, file_name, user): # pylint: disable=unused-variable """ Used to display files that are stored in database. Used in fields like AxImageCropDb""" current_user = user del request, form_guid, file_name with ax_model.scoped_session("routes.db_file_viewer") as db_session: safe_row_guid = str(uuid.UUID(str(row_guid))) ax_field = db_session.query(AxField).filter( AxField.guid == uuid.UUID(field_guid) ).first() # first we select only guid and axState to know, if user have # access row_result = await ax_dialects.dialect.select_one( db_session=db_session, form=ax_field.form, fields_list=[], row_guid=safe_row_guid) state_name = row_result[0]['axState'] state_guid = await ax_auth.get_state_guid( ax_form=ax_field.form, state_name=state_name) user_guid = current_user.get( 'user_id', None) if current_user else None user_is_admin = current_user.get( 'is_admin', False) if current_user else False allowed_field_dict = await ax_auth.get_allowed_fields_dict( ax_form=ax_field.form, user_guid=user_guid, state_guid=state_guid) field_guid = str(ax_field.guid) if not user_is_admin: if (field_guid not in allowed_field_dict or allowed_field_dict[field_guid] == 0 or allowed_field_dict[field_guid] is None): email = current_user.get('email', None) msg = ( f'Error in db_file_viewer. ', f'not allowed for user [{email}]' ) logger.error(msg) return response.text("", status=403) field_value = await ax_dialects.dialect.select_field( db_session=db_session, form_db_name=ax_field.form.db_name, field_db_name=ax_field.db_name, row_guid=safe_row_guid) return response.raw( field_value, content_type='application/octet-stream')
async def retrieve_refresh_token(request, user_id, *args, **kwargs): """ Get refresh token from cache and refresh all perms cache for user """ del args, kwargs device_guid = request.json['deviceGuid'] key = f'refresh_token_{device_guid}_{user_id}' refresh_token = await ax_cache.cache.get(key) if refresh_token: with ax_model.scoped_session("Auth.store_refresh_token") as db_session: await check_if_admin(user_guid=user_id, db_session=db_session) await write_perm_cache(db_session=db_session, user_guid=user_id) return refresh_token
def create_tables() -> None: """Create database and create baseline version in Alembic""" with ax_model.scoped_session("migration -> create_tables") as db_session: if os.environ.get('AX_DB_REVISION') is None: msg = 'Cant find AX_DB_REVISION in enviroment variables or app.yaml' logger.error(msg) raise Exception(msg) # ax_model.Base.query = db_session.query_property() ax_model.Base.metadata.create_all(ax_model.engine) first_version = AxAlembicVersion() first_version.version_num = os.environ.get('AX_DB_REVISION') db_session.add(first_version) logger.info('Ax tables not found. Creating database tables.') return True
def custom_query(self, sql, variables=None): """ Executes any SQL. Used in action python code. This method is SYNC, leave it so Args: db_session: SqlAlchemy session sql (str): Any sql that needs to be executed variables (Dict): Arguments used in sql query Returns: List(Dict(column_name: value)): result of SQL query """ with ax_model.scoped_session("SQL - custom_query") as db_session: res = db_session.execute(sql, variables) if res and res.returns_rows: return res.fetchall() return res
def create_default_pages(): """ Create default page """ with ax_model.scoped_session( "migration -> create_default_pages") as db_session: index_page = AxPage() index_page.name = "Index Page" index_page.code = (f"<h1>Welcome to Ax pages</h1>\n" f"<br/><br/>Hello world") db_session.add(index_page) all_user = db_session.query(AxUser).filter( AxUser.is_all_users.is_(True)).first() p2u = AxPage2Users() p2u.page_guid = index_page.guid p2u.user_guid = all_user.guid db_session.add(p2u) db_session.commit()
def create_default_users(): """ Create Admin group, Default admin, Everyone, All users """ msg = "Error -> Migration -> create_default_users" with ax_model.scoped_session(msg) as db_session: # Admin group admin_group = AxUser() # admin_group.short_name = 'users.admin-group-name' admin_group.short_name = 'Administrators' admin_group.is_group = True admin_group.is_admin = True db_session.add(admin_group) # Default admin admin = AxUser() admin.email = '*****@*****.**' admin.password = pbkdf2_sha256.hash('deleteme') admin.name = 'Default Administrator. Delete this user.' admin.short_name = 'Default Admin' db_session.add(admin) db_session.commit() group_user = AxGroup2Users() group_user.group_guid = admin_group.guid group_user.user_guid = admin.guid db_session.add(group_user) # Everyone everyone_group = AxUser() # everyone_group.short_name = 'users.everyone-group-name' everyone_group.short_name = 'Everyone' everyone_group.is_group = True everyone_group.is_everyone = True db_session.add(everyone_group) # All users all_group = AxUser() # all_group.short_name = 'users.all-users-group-name' all_group.short_name = 'All users' all_group.is_group = True all_group.is_all_users = True db_session.add(all_group) db_session.commit()
async def retrieve_user(request, payload, *args, **kwargs): """ Get user info. This info is transfered into routes with inject_user """ del request, args, kwargs if payload: user_id = payload.get('user_id') or None if not ax_misc.string_is_guid(user_id): return None email = await ax_cache.cache.get(f'user_email_{user_id}') if not email: msg = "Auth -> retrieve_user" with ax_model.scoped_session(msg) as db_session: user = db_session.query(AxUser).filter( AxUser.guid == ax_misc.guid_or_none(user_id) ).first() if user is not None: # raise exceptions.AuthenticationFailed("User not found.") await check_if_admin( user_guid=user_id, db_session=db_session) await write_perm_cache( db_session=db_session, user_guid=user_id) await write_info_cache(user) email = await ax_cache.cache.get(f'user_email_{user_id}') short_name = await ax_cache.cache.get(f'user_short_name_{user_id}') is_admin = await ax_cache.cache.get(f'user_is_admin_{user_id}') user = { "user_id": str(user_id), "is_admin": is_admin, "short_name": short_name, "email": email } return user else: return None
def init_auth(sanic_app, secret="This is big secret, set me in app.yaml"): """ Initiate sanic-jwt module Copyright (C) 2020 Mikhail Marenov - All Rights Reserved You MAY NOT CHANGE source code of Ax workflow without writen permission author. If you change source code in order to activate PRO features - YOU MAY BE SUBJECT TO HEAVY CIVIL PENALTIES. THESE INCLUDE MONETARY DAMAGES, COURT COSTS, AND ATTORNEYS FEES INCURRED Please read LICENSE.md for more information. """ delta = 60 # seconds initialize(sanic_app, authenticate=authenticate, configuration_class=AxConfiguration, refresh_token_enabled=True, store_refresh_token=store_refresh_token, retrieve_refresh_token=retrieve_refresh_token, retrieve_user=retrieve_user, expiration_delta=delta, cookie_access_token_name='ax_auth', cookie_set=True, cookie_strict=False, login_redirect_url='/signin', secret=secret) with ax_model.scoped_session("init_auth - ERROR") as db_session: apply_lise(db_session) # Write cache form Everyone group asyncio.get_event_loop().run_until_complete(write_perm_cache( db_session=db_session, user_guid=None)) # Write cache for dynamic roles asyncio.get_event_loop().run_until_complete(write_dynamic_roles_cache( db_session=db_session))
async def dispatch_request(self, request, *args, **kwargs): """ Sanic view request dispatch. """ start_time = time.time() # TODO this is debug profile with ax_model.scoped_session("GQL error -") as db_session: try: request_method = request.method.lower() data = self.parse_body(request) show_graphiql = request_method == 'get' and self.should_display_graphiql( request) catch = show_graphiql pretty = self.pretty or show_graphiql or request.args.get( 'pretty') user = kwargs['user'] or None # auth_header = request.headers['authorization'] # print(auth_header) ax_context = self.get_context(request) ax_context.update({'session': db_session}) ax_context.update({'user': user}) if request_method != 'options': execution_results, all_params = run_http_query( ax_schema.schema, request_method, data, query_data=request.args, batch_enabled=self.batch, catch=catch, # Execute options return_promise=self._enable_async, root_value=self.get_root_value(request), context_value=ax_context, middleware=self.get_middleware(request), executor=self.get_executor(request), ) # pylint: disable=unused-argument del all_params awaited_execution_results = await Promise.all( execution_results) result, status_code = encode_execution_results( awaited_execution_results, is_batch=isinstance(data, list), format_error=self.format_error, encode=partial(self.encode, pretty=pretty)) log_reguest(request, (time.time() - start_time)) return HTTPResponse(result, status=status_code, content_type='application/json') else: log_reguest(request, (time.time() - start_time)) return self.process_preflight(request) except HttpQueryError as err: logger.exception(f'graqlView -> {err}') return HTTPResponse(self.encode( {'errors': [default_format_error(err)]}), status=err.status_code, headers=err.headers, content_type='application/json') except Exception as err: # pylint: disable=broad-except logger.exception(f'graqlView -> {err}')
def init_schema_standalone(): """ Initiate GQL schema without db_session """ error_msg = "Schema -> init_schema. Error initiating GraphQL shcema." with ax_model.scoped_session(error_msg) as db_session: init_schema(db_session)
async def file_viewer( # pylint: disable=unused-variable request, form_guid, row_guid, field_guid, file_guid, file_name,\ user=None): """ Used to display files uploaded and stored on disk. Displays temp files too. Used in all fields with upload""" del request current_user = user with ax_model.scoped_session( "routes -> file_viewer") as db_session: # if row_guid is null -> display from /tmp without permissions if not row_guid or row_guid == 'null': tmp_dir = os.path.join(ax_misc.tmp_root_dir, file_guid) file_name = os.listdir(tmp_dir)[0] temp_path = os.path.join(tmp_dir, file_name) return await response.file(temp_path) # get AxForm with row values ax_form = db_session.query(AxForm).filter( AxForm.guid == uuid.UUID(form_guid)).first() ax_form = await form_schema.set_form_values( db_session=db_session, ax_form=ax_form, row_guid=row_guid, current_user=current_user) # Get values from row, field field_values = None for field in ax_form.fields: if field.guid == uuid.UUID(field_guid): if field.value: field_values = json.loads(field.value) if type(field_values) is str: try: field_values = json.loads(field_values) except: pass # Find requested file in value the_file = None for file in field_values: if file['guid'] == file_guid: the_file = file if not the_file: return response.text("", status=404) state_guid = await ax_auth.get_state_guid( ax_form=ax_form, state_name=ax_form.current_state_name) user_guid = current_user.get('user_id', None) if current_user else None user_is_admin = current_user.get( 'is_admin', False) if current_user else False allowed_field_dict = await ax_auth.get_allowed_fields_dict( ax_form=ax_form, user_guid=user_guid, state_guid=state_guid) if not user_is_admin: if (field_guid not in allowed_field_dict or allowed_field_dict[field_guid] == 0 or allowed_field_dict[field_guid] is None): email = current_user['email'] msg = (f'Error in file_viewer. ', f'not allowed for user [{email}]') logger.error(msg) return response.text("", status=403) # if file exists -> return file row_guid_str = str(uuid.UUID(row_guid)) file_path = os.path.join(ax_misc.uploads_root_dir, 'form_row_field_file', form_guid, row_guid_str, field_guid, the_file['guid'], the_file['name']) if not os.path.lexists(file_path): return response.text("", status=404) return await response.file(file_path)