def search_users(exact=False, include_deleted=False, include_pending=False, external=False, allow_system_user=False, **criteria): """Searches for users. :param exact: Indicates if only exact matches should be returned. This is MUCH faster than a non-exact saerch, especially when searching external users. :param include_deleted: Indicates if also users marked as deleted should be returned. :param include_pending: Indicates if also users who are still pending should be returned. :param external: Indicates if identity providers should be searched for matching users. :param allow_system_user: Whether the system user may be returned in the search results. :param criteria: A dict containing any of the following keys: name, first_name, last_name, email, affiliation, phone, address :return: A set of matching users. If `external` was set, it may contain both :class:`~flask_multipass.IdentityInfo` objects for external users not yet in Indico and :class:`.User` objects for existing users. """ criteria = {key: value.strip() for key, value in criteria.iteritems() if value.strip()} if not criteria: return set() query = (build_user_search_query(dict(criteria), exact=exact, include_deleted=include_deleted, include_pending=include_pending) .options(db.joinedload(User.identities), db.joinedload(User.merged_into_user))) found_emails = {} found_identities = {} system_user = set() for user in query: for identity in user.identities: found_identities[(identity.provider, identity.identifier)] = user for email in user.all_emails: found_emails[email] = user if user.is_system and not user.all_emails and allow_system_user: system_user = {user} # external user providers if external: identities = multipass.search_identities(exact=exact, **criteria) for ident in identities: if not ident.data.get('email'): # Skip users with no email continue if ((ident.provider.name, ident.identifier) not in found_identities and ident.data['email'].lower() not in found_emails): found_emails[ident.data['email'].lower()] = ident found_identities[(ident.provider, ident.identifier)] = ident return set(found_emails.viewvalues()) | system_user
def build_user_search_query(criteria, exact=False, include_deleted=False, include_pending=False): unspecified = object() query = User.query.options(db.joinedload(User._all_emails)) if not include_pending: query = query.filter(~User.is_pending) if not include_deleted: query = query.filter(~User.is_deleted) affiliation = criteria.pop('affiliation', unspecified) if affiliation is not unspecified: query = query.join(UserAffiliation).filter(unaccent_match(UserAffiliation.name, affiliation, exact)) email = criteria.pop('email', unspecified) if email is not unspecified: query = query.join(UserEmail).filter(unaccent_match(UserEmail.email, email, exact)) # search on any of the name fields (first_name OR last_name) name = criteria.pop('name', unspecified) if name is not unspecified: if exact: raise ValueError("'name' is not compatible with 'exact'") if 'first_name' in criteria or 'last_name' in criteria: raise ValueError("'name' is not compatible with (first|last)_name") name = name.split() query = query.filter(_build_name_search(name)) for k, v in criteria.iteritems(): query = query.filter(unaccent_match(getattr(User, k), v, exact)) return query
def load_token(access_token, refresh_token=None): if not access_token: return None # ugly hack so we can know in other places that we received a token # e.g. to show an error if there was an invalid token specified but # not if there was no token at all g.received_oauth_token = True try: UUID(hex=access_token) except ValueError: # malformed oauth token return None token = OAuthToken.query.filter_by(access_token=access_token).options( db.joinedload(OAuthToken.application)).first() if not token or not token.application.is_enabled: return None token_id = token.id # avoid DetachedInstanceError in the callback @after_this_request def _update_last_use(response): with db.tmp_session() as sess: # do not modify `token` directly, it's attached to a different session! sess.query(OAuthToken).filter_by(id=token_id).update( {OAuthToken.last_used_dt: now_utc()}) sess.commit() return response return token
def load_token(access_token, refresh_token=None): if not access_token: return None # ugly hack so we can know in other places that we received a token # e.g. to show an error if there was an invalid token specified but # not if there was no token at all g.received_oauth_token = True try: UUID(hex=access_token) except ValueError: # malformed oauth token return None token = OAuthToken.find(access_token=access_token).options(db.joinedload(OAuthToken.application)).first() if not token or not token.application.is_enabled: return None token_id = token.id # avoid DetachedInstanceError in the callback @after_this_request def _update_last_use(response): with db.tmp_session() as sess: # do not modify `token` directly, it's attached to a different session! sess.query(OAuthToken).filter_by(id=token_id).update({OAuthToken.last_used_dt: now_utc()}) sess.commit() return response return token
def search_users(exact=False, include_deleted=False, include_pending=False, external=False, **criteria): unspecified = object() query = User.query.options(db.joinedload(User.identities), db.joinedload(User._all_emails), db.joinedload(User.merged_into_user)) original_criteria = dict(criteria) if not criteria: return set() if not include_pending: query = query.filter(~User.is_pending) if not include_deleted: query = query.filter(~User.is_deleted) organisation = criteria.pop('affiliation', unspecified) if organisation is not unspecified: query = query.join(UserAffiliation).filter(_build_match(UserAffiliation.name, organisation, exact)) email = criteria.pop('email', unspecified) if email is not unspecified: query = query.join(UserEmail).filter(_build_match(UserEmail.email, email, exact)) for k, v in criteria.iteritems(): query = query.filter(_build_match(getattr(User, k), v, exact)) found_emails = {} found_identities = {} for user in query: for identity in user.identities: found_identities[(identity.provider, identity.identifier)] = user for email in user.all_emails: found_emails[email] = user # external user providers if external: from indico.core.auth import multipass identities = multipass.search_identities(exact=exact, **original_criteria) for ident in identities: if ((ident.provider.name, ident.identifier) not in found_identities and ident.data['email'].lower() not in found_emails): found_emails[ident.data['email'].lower()] = ident found_identities[(ident.provider, ident.identifier)] = ident return set(found_emails.viewvalues())
def build_user_search_query(criteria, exact=False, include_deleted=False, include_pending=False, include_blocked=False, favorites_first=False): unspecified = object() query = User.query.distinct(User.id).options( db.joinedload(User._all_emails)) if not include_pending: query = query.filter(~User.is_pending) if not include_deleted: query = query.filter(~User.is_deleted) if not include_blocked: query = query.filter(~User.is_blocked) affiliation = criteria.pop('affiliation', unspecified) if affiliation is not unspecified: query = query.join(UserAffiliation).filter( unaccent_match(UserAffiliation.name, affiliation, exact)) email = criteria.pop('email', unspecified) if email is not unspecified: query = query.join(UserEmail).filter( unaccent_match(UserEmail.email, email, exact)) # search on any of the name fields (first_name OR last_name) name = criteria.pop('name', unspecified) if name is not unspecified: if exact: raise ValueError("'name' is not compatible with 'exact'") if 'first_name' in criteria or 'last_name' in criteria: raise ValueError("'name' is not compatible with (first|last)_name") query = query.filter(_build_name_search(name.replace(',', '').split())) for k, v in criteria.items(): query = query.filter(unaccent_match(getattr(User, k), v, exact)) # wrap as subquery so we can apply order regardless of distinct-by-id query = query.from_self() if favorites_first: query = (query.outerjoin( favorite_user_table, db.and_(favorite_user_table.c.user_id == session.user.id, favorite_user_table.c.target_id == User.id)).order_by( nullslast(favorite_user_table.c.user_id))) query = query.order_by( db.func.lower(db.func.indico.indico_unaccent(User.first_name)), db.func.lower(db.func.indico.indico_unaccent(User.last_name)), User.id) return query
def build_user_search_query(criteria, exact=False, include_deleted=False, include_pending=False, favorites_first=False): unspecified = object() query = User.query.distinct(User.id).options(db.joinedload(User._all_emails)) if not include_pending: query = query.filter(~User.is_pending) if not include_deleted: query = query.filter(~User.is_deleted) affiliation = criteria.pop('affiliation', unspecified) if affiliation is not unspecified: query = query.join(UserAffiliation).filter(unaccent_match(UserAffiliation.name, affiliation, exact)) email = criteria.pop('email', unspecified) if email is not unspecified: query = query.join(UserEmail).filter(unaccent_match(UserEmail.email, email, exact)) # search on any of the name fields (first_name OR last_name) name = criteria.pop('name', unspecified) if name is not unspecified: if exact: raise ValueError("'name' is not compatible with 'exact'") if 'first_name' in criteria or 'last_name' in criteria: raise ValueError("'name' is not compatible with (first|last)_name") query = query.filter(_build_name_search(name.replace(',', '').split())) for k, v in criteria.iteritems(): query = query.filter(unaccent_match(getattr(User, k), v, exact)) # wrap as subquery so we can apply order regardless of distinct-by-id query = query.from_self() if favorites_first: query = (query.outerjoin(favorite_user_table, db.and_(favorite_user_table.c.user_id == session.user.id, favorite_user_table.c.target_id == User.id)) .order_by(nullslast(favorite_user_table.c.user_id))) query = query.order_by(db.func.lower(db.func.indico.indico_unaccent(User.first_name)), db.func.lower(db.func.indico.indico_unaccent(User.last_name)), User.id) return query
def search_users(exact=False, include_deleted=False, include_pending=False, external=False, **criteria): """Searches for users. :param exact: Indicates if only exact matches should be returned. This is MUCH faster than a non-exact saerch, especially when searching external users. :param include_deleted: Indicates if also users marked as deleted should be returned. :param include_pending: Indicates if also users who are still pending should be returned. :param external: Indicates if identity providers should be searched for matching users. :param criteria: A dict containing any of the following keys: first_name, last_name, email, affiliation, phone, address :return: A set of matching users. If `external` was set, it may contain both :class:`~flask_multipass.IdentityInfo` objects for external users not yet in Indico and :class:`.User` objects for existing users. """ unspecified = object() query = User.query.options(db.joinedload(User.identities), db.joinedload(User._all_emails), db.joinedload(User.merged_into_user)) criteria = { key: value.strip() for key, value in criteria.iteritems() if value.strip() } original_criteria = dict(criteria) if not criteria: return set() if not include_pending: query = query.filter(~User.is_pending) if not include_deleted: query = query.filter(~User.is_deleted) organisation = criteria.pop('affiliation', unspecified) if organisation is not unspecified: query = query.join(UserAffiliation).filter( unaccent_match(UserAffiliation.name, organisation, exact)) email = criteria.pop('email', unspecified) if email is not unspecified: query = query.join(UserEmail).filter( unaccent_match(UserEmail.email, email, exact)) for k, v in criteria.iteritems(): query = query.filter(unaccent_match(getattr(User, k), v, exact)) found_emails = {} found_identities = {} for user in query: for identity in user.identities: found_identities[(identity.provider, identity.identifier)] = user for email in user.all_emails: found_emails[email] = user # external user providers if external: identities = multipass.search_identities(exact=exact, **original_criteria) for ident in identities: if not ident.data.get('email'): # Skip users with no email continue if ((ident.provider.name, ident.identifier) not in found_identities and ident.data['email'].lower() not in found_emails): found_emails[ident.data['email'].lower()] = ident found_identities[(ident.provider, ident.identifier)] = ident return set(found_emails.viewvalues())
def search_users(exact=False, include_deleted=False, include_pending=False, external=False, **criteria): """Searches for users. :param exact: Indicates if only exact matches should be returned. This is MUCH faster than a non-exact saerch, especially when searching external users. :param include_deleted: Indicates if also users marked as deleted should be returned. :param include_pending: Indicates if also users who are still pending should be returned. :param external: Indicates if identity providers should be searched for matching users. :param criteria: A dict containing any of the following keys: first_name, last_name, email, affiliation, phone, address :return: A set of matching users. If `external` was set, it may contain both :class:`~flask_multipass.IdentityInfo` objects for external users not yet in Indico and :class:`.User` objects for existing users. """ unspecified = object() query = User.query.options(db.joinedload(User.identities), db.joinedload(User._all_emails), db.joinedload(User.merged_into_user)) criteria = {key: value.strip() for key, value in criteria.iteritems() if value.strip()} original_criteria = dict(criteria) if not criteria: return set() if not include_pending: query = query.filter(~User.is_pending) if not include_deleted: query = query.filter(~User.is_deleted) organisation = criteria.pop('affiliation', unspecified) if organisation is not unspecified: query = query.join(UserAffiliation).filter(unaccent_match(UserAffiliation.name, organisation, exact)) email = criteria.pop('email', unspecified) if email is not unspecified: query = query.join(UserEmail).filter(unaccent_match(UserEmail.email, email, exact)) for k, v in criteria.iteritems(): query = query.filter(unaccent_match(getattr(User, k), v, exact)) found_emails = {} found_identities = {} for user in query: for identity in user.identities: found_identities[(identity.provider, identity.identifier)] = user for email in user.all_emails: found_emails[email] = user # external user providers if external: identities = multipass.search_identities(exact=exact, **original_criteria) for ident in identities: if not ident.data.get('email'): # Skip users with no email continue if ((ident.provider.name, ident.identifier) not in found_identities and ident.data['email'].lower() not in found_emails): found_emails[ident.data['email'].lower()] = ident found_identities[(ident.provider, ident.identifier)] = ident return set(found_emails.viewvalues())
def search_users(exact=False, include_deleted=False, include_pending=False, external=False, allow_system_user=False, **criteria): """Searches for users. :param exact: Indicates if only exact matches should be returned. This is MUCH faster than a non-exact saerch, especially when searching external users. :param include_deleted: Indicates if also users marked as deleted should be returned. :param include_pending: Indicates if also users who are still pending should be returned. :param external: Indicates if identity providers should be searched for matching users. :param allow_system_user: Whether the system user may be returned in the search results. :param criteria: A dict containing any of the following keys: name, first_name, last_name, email, affiliation, phone, address :return: A set of matching users. If `external` was set, it may contain both :class:`~flask_multipass.IdentityInfo` objects for external users not yet in Indico and :class:`.User` objects for existing users. """ criteria = {key: value.strip() for key, value in criteria.iteritems() if value.strip()} if not criteria: return set() query = (build_user_search_query( dict(criteria), exact=exact, include_deleted=include_deleted, include_pending=include_pending) .options(db.joinedload(User.identities), db.joinedload(User.merged_into_user))) found_emails = {} found_identities = {} system_user = set() for user in query: for identity in user.identities: found_identities[(identity.provider, identity.identifier)] = user for email in user.all_emails: found_emails[email] = user if user.is_system and not user.all_emails and allow_system_user: system_user = {user} # external user providers if external: identities = multipass.search_identities(exact=exact, **criteria) for ident in identities: if not ident.data.get('email'): # Skip users with no email continue if ((ident.provider.name, ident.identifier) not in found_identities and ident.data['email'].lower() not in found_emails): found_emails[ident.data['email'].lower()] = ident found_identities[(ident.provider, ident.identifier)] = ident return set(found_emails.viewvalues()) | system_user
def search_users(exact=False, include_deleted=False, include_pending=False, external=False, allow_system_user=False, **criteria): """Searches for users. :param exact: Indicates if only exact matches should be returned. This is MUCH faster than a non-exact saerch, especially when searching external users. :param include_deleted: Indicates if also users marked as deleted should be returned. :param include_pending: Indicates if also users who are still pending should be returned. :param external: Indicates if identity providers should be searched for matching users. :param allow_system_user: Whether the system user may be returned in the search results. :param criteria: A dict containing any of the following keys: first_name, last_name, email, affiliation, phone, address :return: A set of matching users. If `external` was set, it may contain both :class:`~flask_multipass.IdentityInfo` objects for external users not yet in Indico and :class:`.User` objects for existing users. """ unspecified = object() query = User.query.options(db.joinedload(User.identities), db.joinedload(User._all_emails), db.joinedload(User.merged_into_user)) criteria = { key: value.strip() for key, value in criteria.iteritems() if value.strip() } original_criteria = dict(criteria) if not criteria: return set() if not include_pending: query = query.filter(~User.is_pending) if not include_deleted: query = query.filter(~User.is_deleted) affiliation = criteria.pop('affiliation', unspecified) if affiliation is not unspecified: query = query.join(UserAffiliation).filter( unaccent_match(UserAffiliation.name, affiliation, exact)) email = criteria.pop('email', unspecified) if email is not unspecified: query = query.join(UserEmail).filter( unaccent_match(UserEmail.email, email, exact)) abstract = criteria.pop('abstract', unspecified) track = criteria.pop('track', unspecified) query2 = query if abstract is not unspecified: if track is unspecified: query = query.join(Abstract, (Abstract.submitter_id == User.id)).filter( unaccent_match(Abstract.title, abstract, exact)) query2 = query2.join( Abstract, (Abstract.submitter_id == User.id)).filter( unaccent_match(Abstract.title, abstract, exact)).add_columns( User.id, Track.title.label('track'), Abstract.title.label('abstract')) else: query = query.join( Abstract, (Abstract.submitter_id == User.id)).join( Track, (Abstract.accepted_track_id == Track.id)).filter( unaccent_match( Abstract.title, abstract, exact)).filter( Track.title.op("~*")( r'[[:<:]]{}[[:>:]]'.format(track))) query2 = query2.join( Abstract, (Abstract.submitter_id == User.id)).add_columns( User.id, Track.title.label('track'), Abstract.title.label('abstract')).join( Track, (Abstract.accepted_track_id == Track.id)).filter( unaccent_match( Abstract.title, abstract, exact)).filter( Track.title.op("~*")( r'[[:<:]]{}[[:>:]]'.format(track))) elif track is not unspecified: query = query.join(Abstract, (Abstract.submitter_id == User.id)).join( Track, (Abstract.accepted_track_id == Track.id)).filter( Track.title.op("~*")(r'[[:<:]]{}[[:>:]]'.format(track))) query2 = query2.join( Abstract, (Abstract.submitter_id == User.id)).add_columns( User.id, Track.title.label('track'), Abstract.title.label('abstract')).join( Track, (Abstract.accepted_track_id == Track.id)).filter( Track.title.op("~*")( r'[[:<:]]{}[[:>:]]'.format(track))) for k, v in criteria.iteritems(): query = query.filter(unaccent_match(getattr(User, k), v, exact)) for user1 in query: for user2 in query2: if (user1.id == user2.id) and ((abstract is not unspecified) or (track is not unspecified)): user1.abstract = user2.abstract user1.track = user2.track found_emails = {} found_identities = {} system_user = set() for user in query: for identity in user.identities: found_identities[(identity.provider, identity.identifier)] = user for email in user.all_emails: found_emails[email] = user if user.is_system and not user.all_emails and allow_system_user: system_user = {user} # external user providers if external: identities = multipass.search_identities(exact=exact, **original_criteria) for ident in identities: if not ident.data.get('email'): # Skip users with no email continue if ((ident.provider.name, ident.identifier) not in found_identities and ident.data['email'].lower() not in found_emails): found_emails[ident.data['email'].lower()] = ident found_identities[(ident.provider, ident.identifier)] = ident return set(found_emails.viewvalues()) | system_user