async def update_user(database=None, target_username=None, target_user_id=None, **kwargs): """Update user. Accepts same optional kwargs as create_user + last_login. :param database: String. Database name to connect to. (Default: None - use jawaf.auth default) :param target_username: String. Username to search against. :param username: String. Username. :param password: String. Password. Will be stored encoded. :param first_name: String. User's first name. (Default None) :param last_name: String. User's last name. (Default None) :param user_type: String. Optionally segment users with string categories. (Default None) :param is_active: Boolean. If user account is active. (Default True) :param is_staff: Boolean. If user is "staff" and has "admin" level access. (Default False) :param is_superuser: Boolean. If user is a "superuser" and has all permissions without being explicitly assigned. (Default False) :param date_joined: Datetime (with timezone). Date user account was created. :param last_login: Datetime (with timezone). Date user account was last accessed. """ database = _database_key(database) if 'password' in kwargs: kwargs['password'] = make_password(kwargs['password']) if not target_username and not target_user_id: raise Exception('Must provide username or user_id to update') async with Connection(database) as con: if target_username: stmt = user.update().where( user.c.username == target_username).values(**kwargs) else: stmt = user.update().where(user.c.id == target_user_id).values( **kwargs) await con.execute(stmt)
async def query(request): query = sa.select('*').select_from(question) results = 'Questions:\n' async with Connection() as con: row = await con.fetchrow(query) results += '%s\n' % str(row) return text(results)
async def create_group(name, permission_pairs, database=None): """Create a group with specified permissions on targets, plus all join tables. :param name: Group name. :param permission_pairs: Tuple. List of permission names/targets to create. :param database: String. Database id to use, or default for AUTH. :return: Int. Group id created. """ database = database_key(database) async with Connection(database) as con: stmt = group.insert().values(name=name) await con.execute(stmt) query = sa.select('*').select_from(group).where( group.c.name == name).order_by(group.c.id.desc()) grp = await con.fetchrow(query) perms = [] for permission_pair in permission_pairs: stmt = permission.insert().values(**permission_pair) await con.execute(stmt) query = sa.select('*').select_from(permission).order_by( permission.c.id.desc()) perm = await con.fetchrow(query) perms.append(perm) for perm in perms: stmt = group_permission.insert().values( permission_id=perm.get('id'), group_id=grp.get('id') ) await con.execute(stmt) return grp.get('id')
async def check_permission(user_row, name, target, database=None): """Check permission via groups. :param user_row: SQLAlchemy Record. User row to check. :param name: String. Name of permission. :param target: String. Target for permission (eg a table). :return: Boolean. If user has permission on target. """ # TODO: Clean this up. if user_row.get('is_superuser'): return True database = database_key(database) async with Connection(database) as con: query = sa.select('*').select_from(user_group).where( user_group.c.user_id == user_row.get('id')) rows = await con.fetch(query) group_ids = [row.get('group_id') for row in rows] if not group_ids: return False query = sa.select('*').select_from(group_permission).where( group_permission.c.group_id.in_(group_ids)) rows = await con.fetch(query) permission_ids = [row.get('permission_id') for row in rows] if not permission_ids: return False query = sa.select('*').select_from(permission) \ .where(permission.c.id.in_(permission_ids)) \ .where(permission.c.name == name) \ .where(permission.c.target == target) row = await con.fetchrow(query) if row: return True return False
async def query(request): query = sa.select('*').select_from(question) results = 'Questions:\n' async with Connection() as con: row = await con.fetchrow(query) question_text = row['question_text'] results += f'{question_text}\n' return text(results)
async def test_create_tables(test_project, waf): """Test create tables command creates the table from the test app.""" create_tables(['test_app'], warn=False) await waf.create_database_pool('default') async with Connection() as con: await con.execute("INSERT INTO test_app_person VALUES (1, 'test_1')") row = await con.fetchrow('''SELECT * FROM test_app_person''') assert row.id == 1 await waf.close_database_pools()
async def test_generate_password_reset_path(test_project, waf): await waf.create_database_pool('default') async with Connection() as con: query = sa.select('*').select_from( tables.user).where(tables.user.c.username == 'test') row = await con.fetchrow(query) user_id = row.get('id') url = await users.generate_password_reset_path(user_id) assert '/auth/password_reset/' in url await waf.close_database_pools()
async def test_create_user(test_project, waf): """Test creating a user.""" await waf.create_database_pool() await users.create_user(username='******', password='******') async with Connection() as con: query = sa.select('*').select_from( tables.user).where(tables.user.c.username == 'test') row = await con.fetchrow(query) assert users.check_password('pass', row.get('password')) await waf.close_database_pools()
async def test_check_user_reset_access_split_token_bad_user_id( test_project, waf): await waf.create_database_pool('default') async with Connection() as con: query = sa.select('*').select_from(tables.user) row = await con.fetchrow(query) user_id = row.get('id') await users.generate_reset_split_token(user_id) verified = await users.check_user_reset_access('test', 3, 'whatever') assert not verified await waf.close_database_pools()
async def add_user_to_group(user_id, group_id, database=None): """Add user to group :param user_id: Int. User id to add. :param group_id: Int. Group id to add user to. :param database: String. Database id to use, or default for AUTH. """ database = database_key(database) async with Connection(database) as con: stmt = user_group.insert().values( user_id=user_id, group_id=group_id ) await con.execute(stmt)
async def check_user(username='', password='', database=None): """Check a user username/password combination agains the database. :param username: String. Username to check. :param password: String. password to verify. :param database: String. Database name to connect to. (Default: None - use jawaf.auth default) :return: Boolean. If username and password are a valid combination. """ database = _database_key(database) async with Connection(database) as con: query = sa.select('*').select_from(user).where( user.c.username == username) row = await con.fetchrow(query) if not row: return False if check_password(password, row.password): return row return False
async def post(self, request, table_name=None): """Post endpoint. Create a new row. :param request: Sanic Request. :param table_name: Name of the table to access. """ if not check_csrf(request): return json({'message': 'access denied'}, status=403) get_jawaf() table = registry.get(table_name) if not table: return json({'message': 'access denied'}, status=403) async with Connection(table['database']) as con: stmt = table['table'].insert().values(**request.json) await con.execute(stmt) await add_audit_action( 'post', 'admin', table_name, request['session']['user']) return json({'message': 'success'}, status=201)
async def create_user(username, password, first_name=None, last_name=None, email=None, user_type=None, is_staff=False, is_superuser=False, is_active=True, date_joined=None, database=None): """Create a user. :param username: String. Username. :param password: String. Password. Will be stored encoded. :param first_name: String. User's first name. (Default None) :param last_name: String. User's last name. (Default None) :param user_type: String. Optionally segment users with string categories. (Default None) :param is_active: Boolean. If user account is active. (Default True) :param is_staff: Boolean. If user is "staff" and has "admin" level access. (Default False) :param is_superuser: Boolean. If user is a "superuser" and has all permissions without being explicitly assigned. (Default False) :param date_joined: Datetime (with timezone). Date user account was created. :param database: String. Database name to connect to. (Default: None - use jawaf.auth default) """ database = database_key(database) if date_joined is None: date_joined = get_utc(datetime.datetime.now()) async with Connection(database) as con: stmt = user.insert().values(username=username, password=make_password(password), first_name=first_name, last_name=last_name, email=email, user_type=user_type, is_staff=is_staff, is_superuser=is_superuser, is_active=is_active, date_joined=date_joined, last_login=None) await con.execute(stmt)
async def generate_reset_split_token(user_id, database=None): """Generate a password reset token for the current user. :param user_id: Int. User id to generate split token for. :param database: String. Database name to connect to. (Default: None - use jawaf.auth default) :return: String. Joined token. """ database = _database_key(database) selector, verifier = _generate_split_token() async with Connection(database) as con: stmt = user_password_reset.insert().values( user_id=user_id, selector=selector, verifier=hashlib.sha256(verifier).hexdigest(), expires=get_utc(datetime.datetime.now() + datetime.timedelta( hours=settings.AUTH_CONFIG['password_reset_expiration'])), ) await con.execute(stmt) return '%s%s' % (selector.decode('utf-8'), verifier.decode('utf-8'))
async def add_audit_action(name, target, table_name, user_row, database=None): """Add audit action :param name: String. Action name. :param target: String. Action target. :param table_name: String. Table name. :param user_row: SQLAlchemy Record. User row. :param database: String. Database id to use, or default for AUTH. """ database = database_key(database) async with Connection(database) as con: stmt = audit_action.insert().values( name=name, table=table_name, target=target, user_id=user_row.get('id'), username=user_row.get('username'), ) await con.execute(stmt)
async def check_user_reset_access(username, user_id, split_token, database=None): """Check password reset token for the current user. :param username: String. Username to check. :param user_id: Int. User_id.. :param split_token: String. Split token to search for and validate against. :param database: String. Database name to connect to. (Default: None - use jawaf.auth default) """ if username is None or user_id is None: return False database = database_key(database) selector = split_token[:SELECTOR_ENCODED_LENGTH].encode('utf-8') verifier = split_token[SELECTOR_ENCODED_LENGTH:].encode('utf-8') async with Connection(database) as con: query = sa.select('*').select_from(user) \ .where(user.c.username == username) \ .where(user.c.id == user_id) row = await con.fetchrow(query) if not row: # username and id don't match! return False query = sa.select('*').select_from(user_password_reset) \ .where(user_password_reset.c.selector == str(selector)) \ .where(user_password_reset.c.user_id == user_id) row = await con.fetchrow(query) if not row: # Selector not found - invalid link. return False if get_utc(datetime.datetime.now()) > row.get('expires'): # First remove the expired record. stmt = user_password_reset.delete().where( user_password_reset.c.id == row.get('id')) await con.execute(stmt) return False if hashlib.sha256(verifier).hexdigest() == row.get('verifier'): # Only allow this reset token to be used once. stmt = user_password_reset.delete().where( user_password_reset.c.id == row.get('id')) await con.execute(stmt) return True return False
async def get(self, request, table_name=None): """Get endpoint. Retrieve one object by id (url param `id=`) :param request: Sanic Request. :param table_name: Name of the table to access. """ get_jawaf() table = registry.get(table_name) try: target_id = int(request.raw_args.get('id', None)) except: return json({'message': 'no id'}, status=400) if not table: return json({'message': 'access denied'}, status=403) async with Connection(table['database']) as con: query = sa.select('*').select_from(table['table']).where( table['table'].c.id == target_id) result = await con.fetchrow(query) if not result: return json({'message': 'not found', 'data': None}, status=404) return json({'message': 'success', 'data': result}, status=200)
async def delete(self, request, table_name=None): """Delete endpoint. :param request: Sanic Request. :param table_name: Name of the table to access. """ if not check_csrf(request): return json({'message': 'access denied'}, status=403) get_jawaf() table = registry.get(table_name) target_id = request.json.get('id', None) if not target_id: return json({'message': 'no id'}, status=400) if not table: return json({'message': 'access denied'}, status=403) async with Connection(table['database']) as con: stmt = table['table'].delete().where( table['table'].c.id == target_id) await con.execute(stmt) await add_audit_action( 'delete', 'admin', table_name, request['session']['user']) return json({'message': 'success'}, status=200)
async def get(self, request, table_name=None): """Get endpoint. Search for objects by `field`/`value` params. Optionally add `sort`, `limit`, and `offset`. Default is no sort. :param request: Sanic Request. :param table_name: Name of the table to access. """ field = request.raw_args.get('field', '') value = request.raw_args.get('value', '') sort = request.raw_args.get('sort', '') limit = request.raw_args.get('limit', '') offset = request.raw_args.get('offset', '') get_jawaf() table = registry.get(table_name) if not table: return json({'message': 'access denied'}, status=403) if field and value: async with Connection(table['database']) as con: query = sa.select('*').select_from(table['table']).where( getattr(table['table'].c, field).ilike(value)) if sort: if sort[0] == '-': sort = sort.lstrip('-') query = query.order_by( sa.desc(getattr(table['table'].c, sort))) else: query = query.order_by(getattr(table['table'].c, sort)) if limit: query = query.limit(int(limit)) if offset: query = query.offset(int(offset)) results = await con.fetch(query) if results: return json({ 'message': 'success', 'results': [r for r in results]}, status=200) return json({'message': 'no data', 'results': ''}, status=401)
async def test_connection(test_project, waf): """Test the Connection class.""" await waf.create_database_pool('default') async with Connection() as con: assert type(con) == asyncpgsa.connection.SAConnection await waf.close_database_pools()