def authenticate_user(self, user: IUser, login_source:str, location: str=None): """Make the current session logged in session for this particular user.""" request = self.request settings = request.registry.settings require_activation = asbool(settings.get('horus.require_activation', True)) allow_inactive_login = asbool(settings.get('horus.allow_inactive_login', False)) if (not allow_inactive_login) and require_activation and (not user.is_activated()): raise AuthenticationFailure('Your account is not active, please check your e-mail. If your account activation email as expired please request a password reset.') if not user.can_login(): raise AuthenticationFailure('This user account cannot log in at the moment.') user_registry = get_user_registry(request) token = user_registry.get_session_token(user) headers = remember(request, token) # assert headers, "Authentication backend did not give us any session headers" if not location: location = get_config_route(request, 'horus.login_redirect') self.greet_user(user) self.update_login_data(user) e = events.Login(request, user) request.registry.notify(e) return HTTPFound(location=location, headers=headers)
def activate_by_email(self, activation_code: str, location: str = None) -> Response: """Active a user after user after the activation email. * User clicks link in the activation email * User enters the activation code on the form by hand :param activation_code: Activation code for user account. :param location: URL to redirct the user to, after activation. :raise: HTTPNotFound is activation_code is invalid. :return: Redirect to location. """ request = self.request settings = request.registry.settings user_registry = get_user_registry(request) after_activate_url = request.route_url( settings.get('websauna.activate_redirect', 'index')) login_after_activation = asbool( settings.get('websauna.login_after_activation', False)) user = user_registry.activate_user_by_email_token(activation_code) if not user: raise HTTPNotFound("Activation code not found") if login_after_activation: login_service = get_login_service(self.request) return login_service.authenticate_user(user, login_source="email") else: self.request.registry.notify( RegistrationActivatedEvent(self.request, user, None)) return HTTPFound(location=location or after_activate_url)
def check_credentials(self, username: str, password: str) -> UserMixin: """Check if the user password matches. * First try username + password + Then try with email + password :param username: username or email :param password: :raise websauna.system.user.interfaces.AuthenticationFailure: On login problem. :return: User object which was picked """ request = self.request settings = request.registry.settings allow_email_auth = settings.get('websauna.allow_email_auth', True) # Check login with username user_registry = get_user_registry(request) user = user_registry.get_authenticated_user_by_username(username, password) # Check login with email if allow_email_auth and not user: user = user_registry.get_authenticated_user_by_email(username, password) if not user: raise AuthenticationFailure('Invalid username or password.') return user
def check_credentials(self, username: str, password: str) -> UserMixin: """Check if the user password matches. * First try username + password + Then try with email + password :param username: username or email :param password: :raise horus.exceptionsAuthenticationFailure: On login problem. TODO: Exception class to be changed. :return: User object which was picked """ request = self.request settings = request.registry.settings allow_email_auth = settings.get('horus.allow_email_auth', False) # Check login with username user_registry = get_user_registry(request) user = user_registry.get_authenticated_user_by_username( username, password) # Check login with email if allow_email_auth and not user: user = user_registry.get_authenticated_user_by_email( username, password) if not user: raise AuthenticationFailure('Invalid username or password.') return user
def create(request, username, email, password=None, source="command_line", admin=False): """Create a new site user from command line. :param request: :param username: :param email: :param password: :param source: :param admin: Set this user to admin. The first user is always implicitly admin. :return: """ User = get_user_class(request.registry) dbsession = request.dbsession u = dbsession.query(User).filter_by(email=email).first() if u is not None: return u u = User(email=email, username=username) if password: user_registry = get_user_registry(request) user_registry.set_password(u, password) u.registration_source = source u.activated_at = now() dbsession.add(u) dbsession.flush() request.registry.notify(UserCreated(request, u)) if admin: group = dbsession.query(Group).filter_by(name="admin").one_or_none() group.users.append(u) return u
def activate_by_email(self, activation_code: str, location=None) -> Response: """Active a user after user after the activation email. * User clicks link in the activation email * User enters the activation code on the form by hand """ request = self.request settings = request.registry.settings user_registry = get_user_registry(request) after_activate_url = request.route_url(settings.get('websauna.activate_redirect', 'index')) login_after_activation = asbool(settings.get('websauna.login_after_activation', False)) user = user_registry.activate_user_by_email_token(activation_code) if not user: raise HTTPNotFound("Activation code not found") if login_after_activation: login_service = get_login_service(self.request.registry) return login_service.authenticate(self.request, user) else: self.request.registry.notify(RegistrationActivatedEvent(self.request, user, None)) return HTTPFound(location=location or after_activate_url)
def activate_by_email(self, activation_code: str, location=None) -> Response: """Active a user after user after the activation email. * User clicks link in the activation email * User enters the activation code on the form by hand """ request = self.request dbsession = self.request.dbsession settings = request.registry.settings user_registry = get_user_registry(request) after_activate_url = request.route_url( settings.get('horus.activate_redirect', 'index')) login_after_activation = asbool( settings.get('horus.login_after_activation', False)) user = user_registry.activate_user_by_email_token(activation_code) if not user: raise HTTPNotFound("Activation code not found") if login_after_activation: login_service = get_login_service(self.request.registry) return login_service.authenticate(self.request, user) else: self.request.registry.notify( RegistrationActivatedEvent(self.request, user, None)) return HTTPFound(location=location or after_activate_url)
def sign_up(self, user_data: dict) -> Response: """Sign up a new user.""" user_registry = get_user_registry(self.request) user = user_registry.sign_up(registration_source="email", user_data=user_data) # Notify site creator to initialize the admin for the first user self.request.registry.notify(UserCreated(self.request, user)) settings = self.request.registry.settings require_activation = asbool(settings.get('websauna.require_activation', True)) autologin = asbool(settings.get('websauna.autologin', False)) if require_activation: self.create_email_activation(user) elif not autologin: messages.add(self.request, msg_id="msg-sign-up-complete", msg="Sign up complete. Welcome!", kind="success") self.request.registry.notify(NewRegistrationEvent(self.request, user, None, user_data)) self.request.dbsession.flush() # in order to get the id if autologin: login_service = get_login_service(self.request.registry) return login_service.authenticate(self.request, user) else: # not autologin: user must log in just after registering. return render_to_response('login/waiting_for_activation.html', {"user": user}, request=self.request)
def check_credentials(self, username: str, password: str) -> UserMixin: """Check if the user password matches. * First try username + password + Then try with email + password :param username: username or email :param password: :raise horus.exceptionsAuthenticationFailure: On login problem. TODO: Exception class to be changed. :return: User object which was picked """ request = self.request settings = request.registry.settings allow_email_auth = settings.get('horus.allow_email_auth', False) # Check login with username user_registry = get_user_registry(request) user = user_registry.get_authenticated_user_by_username(username, password) # Check login with email if allow_email_auth and not user: user = user_registry.get_authenticated_user_by_email(username, password) if not user: raise AuthenticationFailure('Invalid username or password.') return user
def create_forgot_password_request(self, email, location=None) -> Response: """Create a new email activation token for a user and produce the following screen. * Sets user password reset token * Sends out reset password email * The existing of user with such email should be validated beforehand :raise: CannotResetPasswordException if there is any reason the password cannot be reset. Usually wrong email. """ request = self.request user_registry = get_user_registry(request) reset_info = user_registry.create_password_reset_token(email) if not reset_info: raise CannotResetPasswordException("Cannot reset password for email: {}".format(email)) user, token, expiration_seconds = reset_info link = request.route_url('reset_password', code=token) context = dict(link=link, user=user, expiration_hours=int(expiration_seconds/3600)) send_templated_mail(request, [email,], "login/email/forgot_password", context=context) messages.add(request, msg="Please check your email to continue password reset.", kind='success', msg_id="msg-check-email") if not location: location = get_config_route(request, 'websauna.request_password_reset_redirect') assert location return HTTPFound(location=location)
def reset_password(self, activation_code: str, password: str, location=None) -> Response: """Perform actual password reset operations. User has following password reset link (GET) or enters the code on a form. """ request = self.request user_registry = get_user_registry(request) user = user_registry.get_user_by_password_reset_token(activation_code) if not user: return HTTPNotFound("Activation code not found") user_registry.reset_password(user, password) messages.add( request, msg= "The password reset complete. Please sign in with your new password.", kind='success', msg_id="msg-password-reset-complete") request.registry.notify( PasswordResetEvent(self.request, user, password)) request.registry.notify( UserAuthSensitiveOperation(self.request, user, "password_reset")) location = location or get_config_route( request, 'horus.reset_password_redirect') return HTTPFound(location=location)
def save_changes(self, form: deform.Form, appstruct: dict, obj: User): # Set hashed password user_registry = get_user_registry(self.request) user_registry.set_password(obj, appstruct["password"]) # Drop session kill_user_sessions(self.request, obj, "password_change")
def get_user_for_password_reset_token(self, activation_code: str) -> IUser: """Get a user by activation token. """ request = self.request user_registry = get_user_registry(request) user = user_registry.get_user_by_password_reset_token(activation_code) return user
def authenticate_user(self, user: IUser, login_source: str, location: str = None) -> Response: """Make the current session logged in session for this particular user. How to authenticate user using the login service (assuming you have done password match or related yourself): .. code-block:: python from websauna.system.user.utils import get_login_service def my_view(request): # load user model instance from database # user = ... login_service = get_login_service(request) response = login_service.authenticate_user(user, "my-login-source") :raise AuthenticationFailure: If login cannot proceed due to disabled user account, etc. :return: HTTPResponse what should happen as post-login action """ request = self.request settings = request.registry.settings require_activation = asbool( settings.get('websauna.require_activation', True)) allow_inactive_login = asbool( settings.get('websauna.allow_inactive_login', False)) if (not allow_inactive_login) and require_activation and ( not user.is_activated()): raise AuthenticationFailure( 'Your account is not active, please check your e-mail. If your account activation email as expired please request a password reset.' ) if not user.can_login(): raise AuthenticationFailure( 'This user account cannot log in at the moment.') user_registry = get_user_registry(request) token = user_registry.get_session_token(user) headers = remember(request, token) # assert headers, "Authentication backend did not give us any session headers" if not location: location = get_config_route(request, 'websauna.login_redirect') self.greet_user(user) self.update_login_data(user) e = events.Login(request, user) request.registry.notify(e) return HTTPFound(location=location, headers=headers)
def validate_user_exists_with_email(node, val): '''Colander validator that ensures a User exists with the email.''' request = node.bindings['request'] user_registry = get_user_registry(request) user = user_registry.get_by_email(val) if not user: raise c.Invalid(node, "Cannot reset password for such email: {}".format(val))
def save_changes(self, form: deform.Form, appstruct: dict, obj: User): """Save the form data. :param form: Form object. :param appstruct: Form data. :param user: User object. """ # Set hashed password user_registry = get_user_registry(self.request) user_registry.set_password(obj, appstruct["password"]) # Drop session kill_user_sessions(self.request, obj, "password_change")
def get_user(session_token: str, request: Request) -> t.Optional[User]: """Extract the logged in user from the request object using Pyramid's authentication framework.""" # user_id = unauthenticated_userid(request) # TODO: Abstract this to its own service like in Warehouse? user_registry = get_user_registry(request) user = None if session_token is not None: user = user_registry.get_user_by_session_token(session_token) # Check through conditions why this user would no longer be valid if user and not user.can_login(): # User account disabled while in mid-session user = None return user
def resolve_principals(session_token: str, request: Request) -> Optional[List[str]]: """Get applied groups and other for the user. This is a callback for :py:class:`pyramid.authentication.SessionAuthenticationPolicy`. * List all groups as ``group:admin`` style strings * List super user as ``superuser:superuser`` style string :return: None if the user is not logged in, otherwise list of principals assigned to the user site wide. """ user_registry = get_user_registry(request) user = user_registry.get_user_by_session_token(session_token) if not user: return None settings = request.registry.settings # Read superuser names from the config superusers = aslist(settings.get("websauna.superusers")) admin_as_superuser = asbool( settings.get("websauna.admin_as_superuser", False)) if user_registry.can_login(user): # Users always get Authenticated special principal principals = [Authenticated] # All groups for this user principals += [ 'group:{}'.format(g.name) for g in user_registry.get_groups(user) ] # Give principal for per user permissions principals += ['user:{}'.format(user.id)] # Allow superuser permission if user.username in superusers or user.email in superusers: # Superuser explicitly listed in the configuration principals.append("superuser:superuser") elif admin_as_superuser and ("group:admin" in principals): # Automatically promote admins to superusers when doing local development principals.append("superuser:superuser") return principals # User not found, user disabled return None
def validate_user_exists_with_email(node: c.SchemaNode, value: str): """Colander validator that ensures a User exists with the email.' :param node: Colander SchemaNode. :param value: Email address. :raises: c.Invalid if email is not registered for an User. """ request = node.bindings['request'] user_registry = get_user_registry(request) user = user_registry.get_by_email(value) if not user: raise c.Invalid(node, "Cannot reset password for such email: {email}".format(email=value))
def authenticate_user(self, user: IUser, login_source: str, location: str=None) -> Response: """Make the current session logged in session for this particular user. How to authenticate user using the login service (assuming you have done password match or related yourself): .. code-block:: python from websauna.system.user.utils import get_login_service def my_view(request): # load user model instance from database # user = ... login_service = get_login_service(request) response = login_service.authenticate_user(user, "my-login-source") :raise AuthenticationFailure: If login cannot proceed due to disabled user account, etc. :return: HTTPResponse what should happen as post-login action """ request = self.request settings = request.registry.settings require_activation = asbool(settings.get('websauna.require_activation', True)) allow_inactive_login = asbool(settings.get('websauna.allow_inactive_login', False)) if (not allow_inactive_login) and require_activation and (not user.is_activated()): raise AuthenticationFailure('Your account is not active, please check your e-mail. If your account activation email as expired please request a password reset.') if not user.can_login(): raise AuthenticationFailure('This user account cannot log in at the moment.') user_registry = get_user_registry(request) token = user_registry.get_session_token(user) headers = remember(request, token) # assert headers, "Authentication backend did not give us any session headers" if not location: location = get_config_route(request, 'websauna.login_redirect') self.greet_user(user) self.update_login_data(user) e = events.Login(request, user) request.registry.notify(e) return HTTPFound(location=location, headers=headers)
def resolve_principals(session_token: str, request: Request) -> Optional[List[str]]: """Get applied groups and other for the user. This is a callback for :py:class:`pyramid.authentication.SessionAuthenticationPolicy`. * List all groups as ``group:admin`` style strings * List super user as ``superuser:superuser`` style string :return: None if the user is not logged in, otherwise list of principals assigned to the user site wide. """ user_registry = get_user_registry(request) user = user_registry.get_user_by_session_token(session_token) if not user: return None settings = request.registry.settings # Read superuser names from the config superusers = aslist(settings.get("websauna.superusers")) admin_as_superuser = asbool(settings.get("websauna.admin_as_superuser", False)) if user_registry.can_login(user): # Users always get Authenticated special principal principals = [Authenticated] # All groups for this user principals += ['group:{}'.format(g.name) for g in user_registry.get_groups(user)] # Give principal for per user permissions principals += ['user:{}'.format(user.id)] # Allow superuser permission if user.username in superusers or user.email in superusers: # Superuser explicitly listed in the configuration principals.append("superuser:superuser") elif admin_as_superuser and ("group:admin" in principals): # Automatically promote admins to superusers when doing local development principals.append("superuser:superuser") return principals # User not found, user disabled return None
def create_forgot_password_request(self, email: str, location: str = None) -> Response: """Create a new email activation token for a user and produce the following screen. * Sets user password reset token * Sends out reset password email * The existing of user with such email should be validated beforehand :param email: User email. :param location: URL to redirect the user after the password request. :return: Redirect to location. :raise: CannotResetPasswordException if there is any reason the password cannot be reset. Usually wrong email. """ request = self.request user_registry = get_user_registry(request) reset_info = user_registry.create_password_reset_token(email) if not reset_info: raise CannotResetPasswordException( "Cannot reset password for email: {email}".format(email=email)) user, token, expiration_seconds = reset_info link = request.route_url('reset_password', code=token) context = dict(link=link, user=user, expiration_hours=int(expiration_seconds / 3600)) send_templated_mail(request, [ email, ], "login/email/forgot_password", context=context) messages.add(request, msg="Please check your email to continue password reset.", kind='success', msg_id="msg-check-email") if not location: location = get_config_route( request, 'websauna.request_password_reset_redirect') assert location return HTTPFound(location=location)
def create_email_activation(self, user: IUser): """Create through-the-web user sign up with his/her email. We don't want to force the users to pick up an usernames, so we just generate an username. The user is free to change their username later. """ user_registry = get_user_registry(self.request) activation_code, expiration_seconds = user_registry.create_email_activation_token(user) context = { 'link': self.request.route_url('activate', code=activation_code), 'expiration_hours': int(expiration_seconds/3600), } logger.info("Sending sign up email to %s", user.email) # TODO: Broken abstraction, we assume user.email is a attribute send_templated_mail(self.request, [user.email], "login/email/activate", context)
def resolve_principals(session_token: str, request: Request) -> Optional[List[str]]: """Get applied groups and other for the user. This is a callback for :py:class:`pyramid.authentication.SessionAuthenticationPolicy`. * List all groups as ``group:admin`` style strings * List super user as ``superuser:superuser`` style string """ # TODO: Abstract this to its own service like in Warehouse? user_registry = get_user_registry(request) user = user_registry.get_user_by_session_token(session_token) if not user: return None settings = request.registry.settings # Read superuser names from the config superusers = aslist(settings.get("websauna.superusers")) admin_as_superuser = asbool( settings.get("websauna.admin_as_superuser", False)) if user_registry.can_login(user): principals = [ 'group:{}'.format(g.name) for g in user_registry.get_groups(user) ] # Allow superuser permission if user.username in superusers or user.email in superusers: # Superuser explicitly listed in the configuration principals.append("superuser:superuser") elif admin_as_superuser and ("group:admin" in principals): # Automatically promote admins to superusers when doing local development principals.append("superuser:superuser") return principals # User not found, user disabled return None
def get_user(session_token: str, request: Request) -> User: """Extract the logged in user from the request object using Pyramid's authentication framework.""" # user_id = unauthenticated_userid(request) # TODO: Abstract this to its own service like in Warehouse? user_registry = get_user_registry(request) if session_token is not None: user = user_registry.get_user_by_session_token(session_token) # Check through conditions why this user would no longer be valid if user: if not user.can_login(): # User account disabled while in mid-session return None return user return None
def reset_password(self, activation_code: str, password: str, location=None) -> Response: """Perform actual password reset operations. User has following password reset link (GET) or enters the code on a form. """ request = self.request user_registry = get_user_registry(request) user = user_registry.get_user_by_password_reset_token(activation_code) if not user: return HTTPNotFound("Activation code not found") user_registry.reset_password(user, password) messages.add(request, msg="The password reset complete. Please sign in with your new password.", kind='success', msg_id="msg-password-reset-complete") request.registry.notify(PasswordResetEvent(self.request, user, password)) request.registry.notify(UserAuthSensitiveOperation(self.request, user, "password_reset")) location = location or get_config_route(request, 'websauna.reset_password_redirect') return HTTPFound(location=location)
def create( request, username: str, email: str, password: t.Optional[str]=None, source: str='command_line', admin: bool=False ) -> IUserModel: """Create a new site user from command line. :param request: :param username: Username, usually an email. :param email: User's email. :param password: Password. :param source: Source of this user, in here, command_line. :param admin: Set this user to admin. The first user is always implicitly admin. :return: Newly created user. """ User = get_user_class(request.registry) dbsession = request.dbsession u = dbsession.query(User).filter_by(email=email).first() if u is not None: return u u = User(email=email, username=username) dbsession.add(u) dbsession.flush() # Make sure u.user_data is set if password: user_registry = get_user_registry(request) user_registry.set_password(u, password) u.registration_source = source u.activated_at = now() request.registry.notify(UserCreated(request, u)) if admin: group = dbsession.query(Group).filter_by(name='admin').one_or_none() group.users.append(u) return u
def create(request, username, email, password=None, source="command_line", admin=False): """Create a new site user from command line. :param request: :param username: :param email: :param password: :param source: :param admin: Set this user to admin. The first user is always implicitly admin. :return: """ User = get_user_class(request.registry) dbsession = request.dbsession u = dbsession.query(User).filter_by(email=email).first() if u is not None: return u u = User(email=email, username=username) dbsession.add(u) dbsession.flush() # Make sure u.user_data is set if password: user_registry = get_user_registry(request) user_registry.set_password(u, password) u.registration_source = source u.activated_at = now() request.registry.notify(UserCreated(request, u)) if admin: group = dbsession.query(Group).filter_by(name="admin").one_or_none() group.users.append(u) return u
def get_or_create_user(request, email, password): User = get_user_class(request.registry) dbsession = request.dbsession u = dbsession.query(User).filter_by(email=email).first() if u is not None: return u u = User(email=email, username=email) dbsession.add(u) dbsession.flush() # Make sure u.user_data is set if password: user_registry = get_user_registry(request) user_registry.set_password(u, password) u.registration_source = "command_line" u.activated_at = now() request.registry.notify(UserCreated(request, u)) return u
def authenticate_user(self, user: IUser, login_source: str, location: str = None): """Make the current session logged in session for this particular user.""" request = self.request settings = request.registry.settings require_activation = asbool( settings.get('horus.require_activation', True)) allow_inactive_login = asbool( settings.get('horus.allow_inactive_login', False)) if (not allow_inactive_login) and require_activation and ( not user.is_activated()): raise AuthenticationFailure( 'Your account is not active, please check your e-mail. If your account activation email as expired please request a password reset.' ) if not user.can_login(): raise AuthenticationFailure( 'This user account cannot log in at the moment.') user_registry = get_user_registry(request) token = user_registry.get_session_token(user) headers = remember(request, token) # assert headers, "Authentication backend did not give us any session headers" if not location: location = get_config_route(request, 'horus.login_redirect') self.greet_user(user) self.update_login_data(user) e = events.Login(request, user) request.registry.notify(e) return HTTPFound(location=location, headers=headers)
def resolve_principals(session_token: str, request: Request) -> Optional[List[str]]: """Get applied groups and other for the user. This is a callback for :py:class:`pyramid.authentication.SessionAuthenticationPolicy`. * List all groups as ``group:admin`` style strings * List super user as ``superuser:superuser`` style string """ # TODO: Abstract this to its own service like in Warehouse? user_registry = get_user_registry(request) user = user_registry.get_user_by_session_token(session_token) if not user: return None settings = request.registry.settings # Read superuser names from the config superusers = aslist(settings.get("websauna.superusers")) admin_as_superuser = asbool(settings.get("websauna.admin_as_superuser", False)) if user_registry.can_login(user): principals = ['group:{}'.format(g.name) for g in user_registry.get_groups(user)] # Allow superuser permission if user.username in superusers or user.email in superusers: # Superuser explicitly listed in the configuration principals.append("superuser:superuser") elif admin_as_superuser and ("group:admin" in principals): # Automatically promote admins to superusers when doing local development principals.append("superuser:superuser") return principals # User not found, user disabled return None
def reset_password(self, activation_code: str, password: str, location: str = None) -> Response: """Perform actual password reset operations. User has following password reset link (GET) or enters the code on a form. :param activation_code: Activation code provided by the user. :param password: New user password. :param location: URL to redirect the user after the password request. :return: Redirect to location. :raise: HTTPNotFound if activation_code is not found. """ request = self.request user_registry = get_user_registry(request) user = user_registry.get_user_by_password_reset_token(activation_code) if not user: return HTTPNotFound("Activation code not found") user_registry.reset_password(user, password) messages.add( request, msg= "The password reset complete. Please sign in with your new password.", kind='success', msg_id="msg-password-reset-complete") request.registry.notify( PasswordResetEvent(self.request, user, password)) request.registry.notify( UserAuthSensitiveOperation(self.request, user, "password_reset"), request) location = location or get_config_route( request, 'websauna.reset_password_redirect') return HTTPFound(location=location)
def main(argv=sys.argv): if len(argv) < 3: usage(argv) config_uri = argv[1] request = init_websauna(config_uri) User = get_user_class(request.registry) dbsession = request.dbsession if len(argv) == 4: password = argv[3] else: password = getpass.getpass("Password:"******"Password (again):") if password != password2: sys.exit("Password did not match") with transaction.manager: u = User(email=argv[2], username=argv[2]) if password: user_registry = get_user_registry(request) user_registry.set_password(u, password) u.registration_source = "command_line" u.activated_at = now() dbsession.add(u) dbsession.flush() request.registry.notify(UserCreated(request, u)) print("Created user #{}: {}, admin: {}".format(u.id, u.email, u.is_admin()))