def login_with_jwt(): token = request.args.get('token') if not token: return abort(400) try: user_info = jwt.decode(token, get_config()['sessions_secret'], algorithms=['HS256']) if 'id' in user_info: authentication.login(user_info['method'], user_id=user_info['id']) else: if get_organization_id_for_email(user_info['email']) != user_info['organization']: logging.warning('Attempt to use JWT with mismatched org: %s', token) return abort(400) authentication.login(user_info['method'], user_email=user_info['email']) except jwt.DecodeError: logging.warning('Attempt to use invalid JWT: %s', token) return abort(400) except jwt.ExpiredSignatureError: logging.warning('Attempt to use expired JWT: %s', token) return redirect('/')
def test_get_organization_id_for_email__corp_domain( self, mock_get_organization_config): self.assertEqual( 'optimizely.com', utils.get_organization_id_for_email('*****@*****.**')) mock_get_organization_config.assert_called_once_with('optimizely.com')
def get(self): if self.session.get('pickled_oauth_flow'): flow = pickle.loads(self.session['pickled_oauth_flow']) else: flow = flow_from_clientsecrets( get_path_to_oauth_secrets(), scope='https://www.googleapis.com/auth/userinfo.email', redirect_uri='https://trot.to/_/auth/oauth2_callback') if not self.session.get('oauth_state') or self.session.get( 'oauth_state') != self.request.get('state'): self.redirect('/_/auth/login') return try: credentials = flow.step2_exchange(self.request.get('code')) except (FlowExchangeError, ValueError): # user declined to auth; move on self.redirect(self.session.get('redirect_to_after_oauth', '/')) return self.session['credentials'] = pickle.dumps(credentials) self.session['user_email'] = authentication.get_user_email(credentials) user = get_or_create_user( self.session['user_email'], get_organization_id_for_email(self.session['user_email'])) if not user.accepted_terms_at: # all login methods now have UI for consenting to terms user.accepted_terms_at = datetime.datetime.utcnow() user.put() self.redirect(self.session.get('redirect_to_after_oauth', '/'))
def login(authentication_method, user_id=None, user_email=None): if user_id: user = get_user_by_id(user_id) if not user: logging.warning('Attempt to sign in nonexistent user %s', user_id) abort(400) else: user = get_or_create_user(user_email, get_organization_id_for_email(user_email)) allowed_authentication_methods = get_allowed_authentication_methods( user.organization) if allowed_authentication_methods is not None and authentication_method not in allowed_authentication_methods: logging.warning( "User %s attempted to authenticate with method '%s'. Allowed methods are %s.", user.id, authentication_method, allowed_authentication_methods) abort( redirect( f'/_/auth/login?e=auth_not_allowed-{authentication_method}')) if not user.accepted_terms_at: # all login methods now have UI for consenting to terms user.accepted_terms_at = datetime.datetime.utcnow() user.put() login_user(user)
def login_test_user(): if os.getenv('ENVIRONMENT') == 'test_env' and request.headers.get( 'TROTTO_USER_UNDER_TEST'): login_user( get_or_create_user( request.headers.get('TROTTO_USER_UNDER_TEST'), get_organization_id_for_email( request.headers.get('TROTTO_USER_UNDER_TEST'))))
def attempt_auth_by_user_header(self): # only for testing if not env.current_env_is_local(): return if self.request.headers.get('TROTTO_USER_UNDER_TEST'): self.user_email = self.request.headers.get( 'TROTTO_USER_UNDER_TEST') self.user_org = get_organization_id_for_email(self.user_email)
def login_email(user_email): user = get_or_create_user(user_email, get_organization_id_for_email(user_email)) if not user.accepted_terms_at: # all login methods now have UI for consenting to terms user.accepted_terms_at = datetime.datetime.utcnow() user.put() login_user(user)
def dispatch(self): if self.request.host == 'dev.trot.to': if not users.get_current_user(): self.redirect(users.create_login_url('/')) return elif not users.is_current_user_admin(): self.abort(403) self.is_local_env = not os.getenv('SERVER_SOFTWARE', '').startswith('Google App Engine/') # Get a session store for this request. self.session_store = sessions.get_store(request=self.request) self.login_error = None self.user_email = None self.user = None if not self.user_email: self.user_email = self.session.get('user_email') self.user_org = get_organization_id_for_email( self.user_email) if self.user_email else None if not self.user_email: self.attempt_auth_by_emailed_link() if not self.user_email and env.current_env_is_local(): self.attempt_auth_by_user_header() self.session['already_accepted_terms'] = False if self.user_email: self.user = get_or_create_user(self.user_email, self.user_org) self.session[ 'already_accepted_terms'] = not not self.user.accepted_terms_at if not self.session.get('csrf_token'): self.session['csrf_token'] = utils.generate_secret(32) try: self.redirect_to = None self.check_authorization() if self.redirect_to: self.response.write( json.dumps({'redirect_to': self.redirect_to})) return # Dispatch the request. webapp2.RequestHandler.dispatch(self) finally: # Save all sessions, IFF secure connection if self.request.scheme == 'https' or self.is_local_env: self.session_store.save_sessions(self.response)
def get(self): if self.request.get('r'): # outgoing request self.session['request_data_to_replay'] = str(self.request.get('r')) self.render_login_selector_page(self.request.path_url) return try: original_request_data = json.loads(base64.urlsafe_b64decode(self.session['request_data_to_replay'])) except (KeyError, ValueError): self.redirect('/') return if self.request.get('code'): flow = pickle.loads(self.session['pickled_oauth_flow']) try: credentials = flow.step2_exchange(self.request.get('code')) except (FlowExchangeError, ValueError): # user declined to auth; move on self.redirect(original_request_data['origin']) return self.session['credentials'] = pickle.dumps(credentials) self.session['user_email'] = authentication.get_user_email(credentials) self.user_email = self.session['user_email'] self.user_org = get_organization_id_for_email(self.user_email) else: # assume this was a login via email, which would have been processed in the base handler pass object_data = json.loads(original_request_data['body']) new_link = None try: new_link = helpers.create_short_link(self.user_org, self.user_email, object_data['shortpath'], object_data['destination']) response_data = { 'shortpath': new_link.shortpath, 'destination': new_link.destination_url } except helpers.LinkCreationException as e: response_data = { 'error': str(e) } self.redirect(str(original_request_data['origin'] + '?r=' + base64.urlsafe_b64encode(json.dumps(response_data))))
def attempt_auth_by_emailed_link(self): email = self.request.get('e') secret = self.request.get('s') if not email or not secret: return logging.info('Attempting auth by link: (%s,%s)' % (email, secret)) try: validate_login_link(email, secret) self.user_email = email self.user_org = get_organization_id_for_email( self.user_email) if self.user_email else None self.session['user_email'] = self.user_email except LoginLinkValidationError as e: self.login_error = str(e) return
def login_test_user(): if os.getenv('ENVIRONMENT') == 'test_env' and request.headers.get('TROTTO_USER_UNDER_TEST'): login_user(get_or_create_user(request.headers.get('TROTTO_USER_UNDER_TEST'), get_organization_id_for_email(request.headers.get('TROTTO_USER_UNDER_TEST')))) session['last_signin'] = datetime.datetime.utcnow()
def test_get_organization_id_for_email__aliased_domain( self, mock_get_organization_config): self.assertEqual('itso.io', utils.get_organization_id_for_email('*****@*****.**')) mock_get_organization_config.assert_called_once_with('itso.co')
def test_get_organization_id_for_email__special_test_org(self): self.assertEqual( 'test_org', utils.get_organization_id_for_email('*****@*****.**'))
def test_get_organization_id_for_email__generic_domain(self): self.assertEqual('*****@*****.**', utils.get_organization_id_for_email('*****@*****.**'))
def extract_organization(self): return get_organization_id_for_email( self.email) if self.email else None
def __init__(self, **kwargs): super().__init__(**kwargs) self.organization = get_organization_id_for_email(kwargs['email'])
def get_organization_name(self): return get_organization_id_for_email( self.email) if self.email else None
def _extract_domain_type(email): return 'generic' if '@' in get_organization_id_for_email( email) else 'corporate'
def upsert_short_link(organization, owner, namespace, shortpath, destination, updated_link_object): shortpath = shortpath.strip().lower().strip('/') destination = _encode_ascii_incompatible_chars(destination.strip()) if not shortpath: raise LinkCreationException('You must provide a path') PATH_RESTRICTIONS_ERROR = 'Keywords can include only letters, numbers, hyphens, "/", and "%s" placeholders' if shortpath != re.sub('[^0-9a-zA-Z\-\/%]', '', shortpath): raise LinkCreationException(PATH_RESTRICTIONS_ERROR) check_namespaces(organization, namespace, shortpath) if organization != get_organization_id_for_email(owner): raise LinkCreationException("The go link's owner must be in the go link's organization") shortpath_parts = shortpath.split('/') if len(shortpath_parts) > 1: placeholder_found = False for part in shortpath_parts[1:]: if part == '%s': placeholder_found = True elif placeholder_found: raise LinkCreationException('After the first "%s" placeholder, you can only have additional placeholders') if '%' in shortpath: if '%' in shortpath and shortpath.count('%') != shortpath.count('%s'): raise LinkCreationException(PATH_RESTRICTIONS_ERROR) if '%s' in shortpath.split('/')[0]: raise LinkCreationException('"%s" placeholders must come after a "/". Example: "jira/%s"') if shortpath.count('%s') != destination.count('%s'): raise LinkCreationException('The keyword and the destination must have the same number of "%s" placeholders') if not updated_link_object: existing_link, _ = get_shortlink(organization, namespace, shortpath) if existing_link: error_message = 'That go link already exists. %s/%s points to %s' % (namespace, shortpath, existing_link.destination_url) if existing_link.shortpath != shortpath: error_message = 'A conflicting go link already exists. %s/%s points to %s' % (namespace, existing_link.shortpath, existing_link.destination_url) raise LinkCreationException(error_message) if '%s' in shortpath: conflicting_link = find_conflicting_link(organization, namespace, shortpath) if conflicting_link: raise LinkCreationException('A conflicting go link already exists. %s/%s points to %s' % (namespace, conflicting_link.shortpath, conflicting_link.destination_url)) # Note: urlparse('128.90.0.1:8080/start').scheme returns '128.90.0.1'. Hence the additional checking. destination_url_scheme = urlparse(destination).scheme if (not destination_url_scheme or not destination_url_scheme.strip() or not destination.startswith(destination_url_scheme + '://')): # default to HTTP (see Slack discussion) destination = 'http://' + destination if type(validators.url(destination)) is ValidationFailure: raise LinkCreationException('You must provide a valid destination URL') if updated_link_object: link = updated_link_object else: link_kwargs = {'organization': organization, 'owner': owner, 'namespace': namespace, 'shortpath': shortpath, 'destination_url': destination, 'shortpath_prefix': shortpath.split('/')[0]} link = models.ShortLink(**link_kwargs) link.put() link_dict = convert_entity_to_dict(link, ['owner', 'shortpath', 'destination_url', 'organization']) link_dict['id'] = link.get_id() enqueue_event('link.%s' % ('updated' if updated_link_object else 'created'), 'link', link_dict) return link