class RHSignURL(RH): """Create a persistent signed URL for a user. This build a url and adds a signature that authenticates the user without requiring them to have session cookies. It is meant for cases where the user actively requests a persistent link to use outside their browser, e.g. for a calendar feed. When called without authentication, no token is added, so it just behaves as the normal ``url_for`` in order to allow client-side code to be more straightforward and always call this API regardless of whether the user is authenticated or not. """ @use_kwargs({ 'endpoint': fields.String(required=True, validate=validate_with_message(lambda ep: ep in current_app.view_functions, 'Invalid endpoint')), 'params': fields.Dict(keys=fields.String(), load_default={}, validate=validate_with_message(lambda params: not any(x.startswith('_') for x in params), 'Params starting with an underscore are not allowed')) }) def _process(self, endpoint, params): try: if session.user: url = signed_url_for_user(session.user, endpoint, _external=True, **params) Logger.get('url_signing').info("%s signed URL for endpoint '%s' with params %r", session.user, endpoint, params) else: url = url_for(endpoint, _external=True, **params) except BuildError as exc: # if building fails for a valid endpoint we can be pretty sure that it's due to # some required params missing abort(422, messages={'params': [str(exc)]}) return jsonify(url=url)
class RHAPISearch(RH): """API for searching across all records with the current search provider. Besides pagination, filters or placeholders may be passed as query parameters. Since `type` may be a list, the results from the search provider are not mixed with the InternalSearch. """ @use_kwargs( { 'page': fields.Int(missing=None), 'q': fields.String(required=True), 'type': fields.List(EnumField(SearchTarget), missing=None), 'admin_override_enabled': fields.Bool( missing=False, validate=validate_with_message( lambda value: session.user and session.user.is_admin, 'Restricted to admins')), }, location='query', unknown=INCLUDE) def _process(self, page, q, type, **params): search_provider = get_search_provider() if type == [SearchTarget.category]: search_provider = InternalSearch result = search_provider().search(q, session.user, page, type, **params) return ResultSchema().dump(result)
class RHUserSearch(RHProtected): """Search for users based on given criteria.""" def _serialize_pending_user(self, entry): first_name = entry.data.get('first_name') or '' last_name = entry.data.get('last_name') or '' full_name = f'{first_name} {last_name}'.strip() or 'Unknown' affiliation = entry.data.get('affiliation') or '' email = entry.data['email'].lower() ext_id = f'{entry.provider.name}:{entry.identifier}' # detailed data to put in redis to create a pending user if needed self.externals[ext_id] = { 'first_name': first_name, 'last_name': last_name, 'email': email, 'affiliation': affiliation, 'phone': entry.data.get('phone') or '', 'address': entry.data.get('address') or '', } # simple data for the search results return { '_ext_id': ext_id, 'id': None, 'identifier': f'ExternalUser:{ext_id}', 'email': email, 'affiliation': affiliation, 'full_name': full_name, 'first_name': first_name, 'last_name': last_name, } def _serialize_entry(self, entry): if isinstance(entry, User): return search_result_schema.dump(entry) else: return self._serialize_pending_user(entry) def _process_pending_users(self, results): cache = make_scoped_cache('external-user') for entry in results: ext_id = entry.pop('_ext_id', None) if ext_id is not None: cache.set(ext_id, self.externals[ext_id], timeout=86400) @use_kwargs( { 'first_name': fields.Str(validate=validate.Length(min=1)), 'last_name': fields.Str(validate=validate.Length(min=1)), 'email': fields.Str(validate=lambda s: len(s) > 3), 'affiliation': fields.Str(validate=validate.Length(min=1)), 'exact': fields.Bool(missing=False), 'external': fields.Bool(missing=False), 'favorites_first': fields.Bool(missing=False) }, validate=validate_with_message( lambda args: args.keys( ) & {'first_name', 'last_name', 'email', 'affiliation'}, 'No criteria provided'), location='query') def _process(self, exact, external, favorites_first, **criteria): matches = search_users(exact=exact, include_pending=True, external=external, **criteria) self.externals = {} results = sorted((self._serialize_entry(entry) for entry in matches), key=itemgetter('full_name')) if favorites_first: favorites = {u.id for u in session.user.favorite_users} results.sort(key=lambda x: x['id'] not in favorites) total = len(results) results = results[:10] self._process_pending_users(results) return jsonify(users=results, total=total)
unaccent_match(getattr(EventPerson, k), v, exact)) # wrap as subquery so we can apply order regardless of distinct-by-id query = query.from_self() query = query.order_by( db.func.lower( db.func.indico.indico_unaccent(EventPerson.first_name)), db.func.lower(db.func.indico.indico_unaccent( EventPerson.last_name)), EventPerson.id, ) return query.limit(10).all(), query.count() @use_kwargs( { 'first_name': fields.Str(validate=validate.Length(min=1)), 'last_name': fields.Str(validate=validate.Length(min=1)), 'email': fields.Str(validate=lambda s: len(s) > 3), 'affiliation': fields.Str(validate=validate.Length(min=1)), 'exact': fields.Bool(missing=False), }, validate=validate_with_message( lambda args: args.keys( ) & {'first_name', 'last_name', 'email', 'affiliation'}, 'No criteria provided'), location='query') def _process(self, exact, **criteria): matches, total = self._search_event_persons(exact=exact, **criteria) return jsonify(users=EventPersonSchema().dump(matches, many=True), total=total)