def remove_identifier(self, identifier): if identifier not in self.identifiers: raise errors.ForbiddenAction('That identifier belongs to another account') if len(self.identifiers) < 2: raise errors.ForbiddenAction('Can not remove last identifier') account = models.Identity.release(identifier, assert_account_key=self.account.key) # Update in-memory instance to reflect reality. if account: self.account.populate(**account.to_dict()) # Disconnect service if the identifier is a service identifier. identifier, identifier_type = identifiers.parse(identifier) if identifier_type in (identifiers.EMAIL, identifiers.SERVICE_ID): service, team, resource = identifiers.parse_service(identifier) self.disconnect_service(service, team, resource) self._notify_account_change()
def change_identifier(self, old, new, notify_connect=True, primary=False): new, identifier_type = identifiers.parse(new) if not new: logging.warning('%r is invalid', new) raise errors.InvalidArgument('That identifier is not valid') if old not in self.identifiers: raise errors.ForbiddenAction('That identifier belongs to another account') if old == new: return # Get the service, team, resource from the new identifier. try: service, team, resource = identifiers.parse_service(new) new_team = not self.is_on_team(service, team) except: service, team, resource = (None, None, None) new_team = True identity, account = models.Identity.change( old, new, assert_account_key=self.account.key, primary=primary) if not identity: raise errors.AlreadyExists('That identifier is already in use') # Update in-memory instance to reflect reality. if account: self.account.populate(**account.to_dict()) if self.account.is_activated and service == 'email' and new_team: # Connect the email "service" (if the user is not already on this domain). self.connect_service(service, team, resource, notify=notify_connect) # TODO: We should also disconnect service if the old identifier was a service. self._notify_account_change()
def eject(self, participants, **kwargs): if not isinstance(participants, (list, set)): participants = [participants] account_keys = models.Account.resolve_keys(participants) if self.account.key in account_keys: raise errors.ForbiddenAction( 'Leave instead of removing from participants') self._set_participants(remove=account_keys, **kwargs)
def create_session(self, skip_activation=False, **kwargs): # Activate the user when they log in. if not (self.is_activated or skip_activation) or self.is_inactive: if not self.can_activate: raise errors.ForbiddenAction('Account may not be activated at this time') self.change_status('active', status_reason='logged_in') scopes = {auth.SCOPE_REFRESH_TOKEN} return auth.Session(self.account, scopes=scopes, **kwargs)
def wrap(self, *args, **kwargs): if not self.participant: raise errors.ForbiddenAction( 'Account is not a participant in stream') if len(self._stream.participants) == 2: for p in self._stream.participants: if p.account in self.account.blocked_by: logging.info('Blocked Stream.%s from %s to %s', func.__name__, self.account.key, p.account) return return func(self, *args, **kwargs)
def show(self): t = self._thread.key.get() if self.account_key in t.visible_by: return for a in t.accounts: if a.account == self.account_key: break else: raise errors.ForbiddenAction('Cannot update that thread') t.visible_by.insert(0, self.account_key) t.put() self._thread = t
def _validate_status_transition(old_status, new_status): """Validates that a status may change from a certain value to another.""" can_change = False if old_status is not None else True for tier in config.VALID_STATUS_TRANSITIONS: if old_status in tier: can_change = True if new_status in tier: if not can_change: raise errors.ForbiddenAction('Cannot change status from "%s" to "%s"' % ( old_status, new_status)) break else: raise errors.InvalidArgument('Invalid status')
def hide_for_all(self): # Note: Intended for admin only. t = self._thread.key.get() if not t.visible_by: return for a in t.accounts: if a.account == self.account_key: break else: raise errors.ForbiddenAction('Cannot update that thread') t.visible_by = [] t.put() self._thread = t
def wrap(*args, **kwargs): time_1 = time.clock() session = get_session() if not session or not session.account: raise errors.InvalidAccessToken() # Ensure that the account is active. if session.account.can_make_requests: if update_last_active: # Update last active date if they haven't been active earlier today. today = date.today() if session.account.last_active < today: session.account.last_active = today agent = request.headers.get('User-Agent') if agent: session.account.last_active_client = agent session.account.put() elif not allow_nonactive: logging.warning('Account %d is %s', session.account_id, session.account.status) if session.account.is_activated: # The account has been activated but can't make requests so it's disabled. raise errors.AccountDisabled() else: raise errors.ForbiddenAction('Account is not active') # Update the user's location coordinates if provided. client_id, _ = get_client_details() if session.account.share_location and config.LOCATION_HEADER in request.headers: try: latlng = request.headers[config.LOCATION_HEADER] point = ndb.GeoPt(latlng) session.account.set_location(point) except: pass elif client_id in ( 'fika', 'reactioncam' ) and 'X-AppEngine-CityLatLong' in request.headers: latlng = request.headers['X-AppEngine-CityLatLong'] session.account.set_location(ndb.GeoPt(latlng), timezone_only=True) if set_view_account: g.public_options['view_account'] = session.account time_2 = time.clock() try: result = func(session, *args, **kwargs) finally: time_3 = time.clock() logging.debug('Account %d', session.account_id) return result
def public(self, num_chunks=5, version=None, **kwargs): if not self.participant: raise errors.ForbiddenAction( 'Account is not a participant in stream') # Convert the stream to a dict, including the set account's metadata. data = { 'chunks': self.get_relevant_chunks(num_chunks), 'created': self.created, 'id': self.key.id(), 'joined': self.joined, 'played_until': self.played_until, 'title': self.title, 'total_duration': self.total_duration, 'visible': self.visible, } # Note: Versions before 6 included the current account in the participants list. participants = self.get_others( ) if version >= 6 else self.get_accounts() if version < 11: data['color'] = None if version >= 6: data['last_interaction'] = self.last_interaction data['last_played_from'] = self.last_played_from data['others'] = participants else: data['last_chunk_end'] = self.last_chunk_end data['participants'] = participants if version >= 7: data['image_url'] = self.image_url if 17 <= version < 22: data['service'] = None if version >= 18: data['attachments'] = {a.id: a for a in self.attachments} data['invite_token'] = self.invite_token elif version >= 16: data['attachments'] = self.attachments if version < 23: # This data is available with more info from Participant in v23+. data['others_listened'] = self.others_played if version >= 28: data['service_content_id'] = self.service_content_id if version >= 29: data['service_member_count'] = len(self.service_members) or None return data
def set_seen_until(self, seen_until): t = self._thread.key.get() for m in t.messages: if m.message_id == seen_until: break else: # TODO: Have another look at this logic. raise errors.InvalidArgument( 'Message id must be one of 10 most recent') for a in t.accounts: if a.account == self.account_key: break else: raise errors.ForbiddenAction('Cannot update that thread') if a.seen_until and a.seen_until.id() >= seen_until: return a.seen_until = m.key_with_parent(self.key) t.put() self._thread = t
def get_session(): try: access_token = request.args.get('access_token') if not access_token: authorization = request.headers['Authorization'] token_type, access_token = authorization.split(' ') assert token_type == 'Bearer' session = Session.from_access_token(access_token) except: return None # Allow admins to override the session account id. # TODO: This needs to be checked on the token, so that a token for an # admin granted to a third-party app can't also do this. on_behalf_of = request.args.get('on_behalf_of') if on_behalf_of: if session.account.admin: session = Session(int(on_behalf_of)) else: raise errors.ForbiddenAction('Forbidden use of on_behalf_of') return session
def challenge(self): if self.client == 'fika': # TODO: Support more than email. try: service_key, team_key, _ = models.Service.parse_identifier( self.identifier) except ValueError: raise ValueError('%r could not be used as email' % (self.identifier, )) if team_key.id() in config.PUBLIC_EMAIL_DOMAINS: # Explicitly ignore public email domains. team = None else: # Ensure that the team exists and fetch it. team = models.ServiceTeam.create_or_update(team_key) login_enabled = (team and team.whitelisted) block_due_to_roger = False event = events.FikaLoginV3(blocked=False, identifier=self.identifier) if not login_enabled: # Check the account for another valid team which will let them in. # TODO: Improve this logic. account = models.Account.resolve(self.identifier) event.account_id = account.key.id() if account else None event.has_roger = account.has_roger if account else None if account and account.can_activate and g.api_version >= 34: if not account.has_roger: logging.debug( 'Allowing login for %s (id: %d, status: %r)', self.identifier, account.key.id(), account.status) login_enabled = True else: block_due_to_roger = True logging.debug( 'Not allowing login for Roger account %s (%d)', self.identifier, account.key.id()) elif account: # If we get here, it means the account status is "waiting". logging.debug( '%s (%d) is not whitelisted, checking other teams', self.identifier, account.key.id()) for id_key in account.identifiers: try: s, t, _ = models.Service.parse_identifier( id_key.id()) if s.id() in ('fika', 'slack'): team = t.get() login_enabled = True logging.debug('Allowing login because of %s', id_key.id()) break elif s.id() == 'email' and t != team_key: # Only emails use whitelisting. team = t.get() login_enabled = t.get().whitelisted if login_enabled: logging.debug( 'Allowing login because of another whitelisted email' ) break logging.debug('%s is not whitelisted', id_key.id()) except: logging.debug('Skipping %s', id_key.id()) continue # Enforce block list. if identifiers.email(self.identifier) in config.BLOCKED_EMAILS: login_enabled = False # Block any users who are not whitelisted somehow. if not login_enabled: logging.warning('Blocking %s (not on a valid team)', self.identifier) if block_due_to_roger: # We can assume the account variable is set here. link = 'https://api.rogertalk.com/admin/accounts/%d/' % ( account.key.id(), ) template = 'Blocked Roger user (*{}*): {{}}'.format( '<%s|%s>' % (link, account.display_name)) else: template = 'Blocked login: {}' if identifiers.email( self.identifier) not in config.MUTED_EMAILS: # TODO: Don't assume self.identifier is an email. slack_api.message(channel='#review', hook_id='fika', text=template.format( identifiers.email(self.identifier))) event.blocked = True event.report() raise errors.ForbiddenAction( 'That identifier has not been enabled for login') event.report() self.team = team challenge = models.Challenge.get(self.client, self.identifier, self.code_length) self._deliver(challenge.code)
def set_featured(self, featured): if featured and (not self.title or not self.image): raise errors.ForbiddenAction( 'Stream needs an image and title to be featured') self._tx(models.Stream.set_featured, featured)