def set_user_jwt(auth_key, profile): """ Logs in (or signs up) a new user given its JWT and a default profile """ # Load the most current keys from Redis jwkeys.import_keyset(redis_db.get('jwkeys')) try: jwt = JWT(jwt=auth_key, key=jwkeys) claims = json_loads(jwt.claims) except: # This is called if the authorization failed (auth key has been forged) # We don't really care about providing malicious requests with debug # info so this is just a really basic error message return render_error('rejected.'), 403 # JWT has a couple of fields (claims) that we care about: # - iss: Issuer - Basically provider of open id (google, SE, etc.) # - sub: Subject - Who is auth'd by this. # Together we will use these to store an auth method. issuer = claims.get('iss') subject = claims.get('sub') # Error handle against malformed keys. This should never really happen and # the error is not shown to the user so doesn't need to be user-friendly if not issuer or not subject: return render_error('malformed. Missing iss or sub'), 400 # If we are here that means we have validated a valid login attempt. Now we # will delegate to another method token = UserJWTToken(identity=subject, issuer=issuer) return get_or_set_user(jwt_token=token, profile=profile)
def set_user_oauth(code, provider, client_side=False): """ Logs in an OAuth redirect request given the `provider` describing the type """ oauth_provider = oauth_config.get(provider, None) if oauth_provider is None: return abort(400) oauth_callback = oauth_provider.get('token') oauth_login = oauth_provider.get('auth') oauth_id = oauth_data.get(provider).get('client-id') oauth_secret = oauth_data.get(provider).get('client-secret') try: # Get the auth key. By polling the provider we both validate # the login request and now are able to submit requests as # users of the provider. auth_key = requests.post(oauth_callback, data={ 'code': code, 'redirect_uri': canonical_host + url_for('auth_login_oauth'), 'client_id': oauth_id, 'client_secret': oauth_secret }).json().get('access_token', '') except: # Errors mean we couldn't get access key return render_error('Could not obtain OAuth access token.'), 403 # If we're client-side we'll stop here if client_side: return auth_key try: # Get identity key, this is something that allows us # to uniquely identify the user oauth_identity, profile = oauth_login(auth_key) except: # If we get here that means we could not get profile # this is our fault since we validated return render_error('Could not obtain OAuth profile.'), 500 # Model instance for the token # Links together the provider, provider's ID, and our user ID token = UserOAuthToken(provider_id=provider, identity=oauth_identity) get_or_set_user(oauth_token=token, profile=profile)
def get_following(user_id, page): user = User.query.filter_by(id=user_id).first() if not isinstance(user, User): return render_error('Nonexistent ID'), 404 if not user.following_public: return render_error('Forbidden'), 403 following = user.following.filter_by().paginate(page, user_list['page_len'], error_out=False) return render_json({ 'data': [follower.to_json(current_user=g.user) for follower in following.items], 'are_more': following.has_next })
def category_search(): json = request.get_json(silent=True) if not json or 'query' not in json or len(str(json['query'])) == 0: return render_error('Bad query') return render_json({'results': search_category(query=str(json['query']))})
def mark_notifications_status(notifications, status): """ Marks a list of notification IDs as seen. Requires authorized user in session """ if not isinstance(g.user, User): return render_error('Unauthorized'), 401 if status == NotificationStatus.SEEN: # Basically we won't want "read" messages going to # "seen" state Notification.query.filter( Notification.recipient == g.user, Notification.uuid.in_(notifications), Notification.read == NotificationStatus.UNSEEN).update( {'read': NotificationStatus.SEEN}, synchronize_session=False) else: Notification.query.filter(Notification.recipient == g.user, Notification.uuid.in_(notifications)).update( {'read': status}, synchronize_session=False) db.session.commit() return render_json({'status': status.value})
def auth_login(): json = request.get_json(silent=True) # JSON parsing failed if not json or 'token' not in json: return render_error('bad json') return auth.set_user_jwt(json['token'], json.get('profile'))
def get_profile(user_id): """ Returns a user's user_id """ user = User.query.filter_by(id=user_id).first() if user is None: return render_error('user not found'), 400 else: return render_json(user.to_json())
def remove_auth_method(id, user): """ Removes auth method for user """ auth_tokens = UserAuthToken.query.filter_by(user_id=user.id).all() if len(auth_tokens) <= 1: # You can't remove auth token if you only have 1 return render_error('not enough tokens'), 412 selected_token = next( (auth_token for auth_token in auth_tokens if auth_token.id == id), None) if not isinstance(selected_token, UserAuthToken): return render_error('invalid method'), 404 db.session.delete(selected_token) db.session.commit() return render_json({'id': id})
def auth_login_jwt(): json = request.get_json(silent=True) # JSON parsing failed if not json or 'token' not in json: return render_error('bad json') jwt_errors = auth.set_user_jwt(json['token'], json.get('profile')) if jwt_errors is not None: return jwt_errors else: return render_json({'user_id': g.user.id})
def unfollow(source_user_id, target_user_id): """ Makes 1st param unfollow the 2nd param """ if source_user_id == target_user_id: return render_error('cannot follow oneself'), 400 source_user = User.query.filter_by(id=source_user_id).first() target_user = User.query.filter_by(id=target_user_id).first() if not isinstance(g.user, User): return render_error('Unauthorized'), 401 if source_user is None or target_user is None: return render_error('source user or target user doesn\'t exist'), 400 if source_user.id != g.user.id: return render_error('Forbidden'), 403 source_user.unfollow(target_user) db.session.commit() return render_json({ 'following': False })
def mark_notification_status(notification_id, status): """ Marks a give notification as read. """ if not isinstance(g.user, User): return render_error('Unauthorized'), 401 notifications = Notification.query.\ filter_by(recipient_id=g.user.id, uuid=notification_id) notifications.update({'read': status}) db.session.commit() return render_json({'status': status.value})
def get_or_set_user(jwt_token=None, oauth_token=None, profile={}): """ This will take an auth object and login the user. If the user does not exist, it will save (persist) the auth and create a new account using the profile details. """ if jwt_token: token = UserJWTToken.query.filter_by(identity=jwt_token.identity, issuer=jwt_token.issuer).first() elif oauth_token: token = UserOAuthToken.query.filter_by( identity=oauth_token.identity, provider_id=oauth_token.provider_id).first() else: return abort(500) user = None if token is not None: # This means the user exists user = token.user else: # This means the user does not exist and we must create it. name = profile.get('name') email = profile.get('email') # Make sure we have both fields or return missing error if not name: return render_error("Profile does not have name", error_type='bad_profile'), 400 user = User(name=name, email=email, avatar=profile.get('avatar')) if jwt_token: user.jwt_tokens.append(jwt_token) elif oauth_token: user.oauth_tokens.append(oauth_token) db.session.add(user) db.session.commit() user_session.set_session_user(user) g.user = user ip_address = getattr(request, 'access_route', [request.remote_addr])[0] login = Login(ip_address=ip_address, user_id=g.user.id) db.session.add(login) db.session.commit()
def mark_all_notifications_status(status): """ Marks all notifications as seen. Requires authorized user """ if not isinstance(g.user, User): return render_error('Unauthorized'), 401 if status == NotificationStatus.SEEN: Notification.query.\ filter_by(recipient=g.user, read=NotificationStatus.UNSEEN).\ update({'read': status}) else: Notification.query.\ filter_by(recipient=g.user).\ update({'read': status}) db.session.commit() return render_json({'status': status.value})
def unfollow_user(target_user_id): if not isinstance(g.user, User): return render_error('Unauthorized'), 401 return user.unfollow(g.user.id, target_user_id)
def wrap(*args, **kwargs): if not isinstance(g.user, User): return render_error('Unauthorized'), 401 return f(*args, **kwargs)
def get_or_set_user(auth_token=None, profile={}, auth_opts={}): """ This will take an auth object and login the user. If the user does not exist, it will save (persist) the auth and create a new account using the profile details. """ if isinstance(g.user, User) and g.user.deleted: g.user = None return abort(401) is_append_flow = auth_opts.get('append', False) if auth_token: existing_auth_token = UserAuthToken.query.\ filter_by( auth_method=auth_token.auth_method, identity=auth_token.identity, issuer=auth_token.issuer ).first() else: return abort(500) user = None if existing_auth_token is not None and existing_auth_token.user is not None: # If the login method already belongs to another thing if is_append_flow: return render_error("Already used method", error_type='duplicate_method'), 403 # Set to existing auth token auth_token = existing_auth_token # This means the user exists user = existing_auth_token.user # make sure existing user isn't deleted if user.deleted: return abort(401) else: # If append flow + the user doesn't exist (good), we'll just add the user if is_append_flow: if not isinstance(g.user, User): return render_error("Not authorized", error_type='unauthorized'), 400 # Let's add the auth token to current user g.user.auth_tokens.append(auth_token) db.session.add(auth_token) db.session.commit() db.session.refresh(auth_token) return # This means the user does not exist and we must create it. name = profile.get('name') email = profile.get('email') # Make sure we have both fields or return missing error if not name: return render_error("Profile does not have name", error_type='bad_profile'), 400 user = User(name=name, email=email, avatar=profile.get('avatar')) user.auth_tokens.append(auth_token) db.session.add(user) db.session.commit() # Reload auth token object db.session.refresh(auth_token) user_session.set_session_user(user) g.user = user ip_address = getattr(request, 'access_route', [request.remote_addr])[0] login = Login(ip_address=ip_address, user_id=g.user.id, login_method_id=auth_token.id) db.session.add(login) db.session.commit()
def set_user_oauth(code, provider, client_side=False, auth_opts={}): """ Logs in an OAuth redirect request given the `provider` describing the type """ oauth_provider = oauth_config.get(provider, None) if oauth_provider is None: return abort(400) oauth_callback = oauth_provider.get('token') oauth_login = oauth_provider.get('auth') oauth_provider_identifier = provider oauth_id = oauth_data.get(oauth_provider_identifier).get('client-id') oauth_secret = oauth_data.get(oauth_provider_identifier).get( 'client-secret') try: # Get the auth key. By polling the provider we both validate # the login request and now are able to submit requests as # users of the provider. access_token_response = requests.post(oauth_callback, data={ 'code': code, 'redirect_uri': canonical_host + url_for('auth_login_oauth'), 'client_id': oauth_id, 'client_secret': oauth_secret, 'grant_type': 'authorization_code' }, headers={ 'Accept': 'application/json' }).json() except Exception as error: if bugsnag.configuration.api_key is not None: bugsnag.notify(error, meta_data={'oauth': {'provider': provider}}) # Errors mean we couldn't get access key return render_error('Failed to connect to OAuth provider.'), 403 try: auth_key = access_token_response['access_token'] except Exception as error: if bugsnag.configuration.api_key is not None: bugsnag.notify(error, meta_data={ 'oauth': { 'provider': provider, 'data': access_token_response } }) # Errors mean we couldn't get access key return render_error( 'Could not obtain OAuth access token from OAuth provider.'), 403 is_append_flow = auth_opts.get('append', False) # If we're client-side we'll stop here UNLESS we are appending if client_side and not is_append_flow: return auth_key try: # Get identity key, this is something that allows us # to uniquely identify the user. # The profile['identification'] is a user-readable string. oauth_identity, profile = oauth_login(auth_key) except Exception as error: if bugsnag.configuration.api_key is not None: bugsnag.notify(error, meta_data={'oauth': {'provider': provider}}) # If we get here that means we could not get profile # this is our fault since we validated return render_error( 'Could not obtain OAuth profile using provider implementation.' ), 500 if 'identifier' not in profile: return render_error('Could not obtain identifier'), 400 # Model instance for the token # Links together the provider, provider's ID, and our user ID token = UserAuthToken(auth_method=AuthTokenType.OAUTH, issuer=oauth_provider_identifier, identity=oauth_identity, identifier=profile.get('identifier')) return get_or_set_user(auth_token=token, profile=profile, auth_opts=auth_opts)