class IUserRegistrationInfo(Interface): """User registration info""" login = TextLine( title=USER_LOGIN_TITLE, description=_( "If you don't provide a custom login, your login will be your " "email address..."), required=False) @invariant def check_login(self): """Set login as mail when missing""" if not self.login: self.login = self.email email = TextLine( title=_("E-mail address"), description=_( "An email will be sent to this address to validate account " "activation; it will be used as your future user login"), required=True) @invariant def check_email(self): """Check for valid email""" if not EMAIL_REGEX.match(self.email): raise Invalid(_("Your email address is not valid!")) firstname = TextLine(title=_("First name"), required=True) lastname = TextLine(title=_("Last name"), required=True) company_name = TextLine(title=_("Company name"), required=False) password = EncodedPasswordField( title=_("Password"), description=_("Password must be at least 8 characters long, " "and contain at least three kinds of " "characters between lowercase letters, " "uppercase letters, numbers and special " "characters"), min_length=8, required=True) confirmed_password = EncodedPasswordField(title=_("Confirmed password"), required=True) @invariant def check_password(self): """Check for password confirmation""" if self.password != self.confirmed_password: raise Invalid(_("You didn't confirmed your password correctly!")) check_password(self.password)
class ISiteRootRoles(IContentRoles): """Site root roles""" managers = PrincipalsSetField( title=_("Site managers"), description=_("These principals are allowed to manage the " "whole application environment"), role_id=SYSTEM_ADMIN_ROLE, required=False) viewers = PrincipalsSetField( title=_("Site viewers"), description=_("These principals are allowed to view some " "application settings, without update"), role_id=SYSTEM_VIEWER_ROLE, required=False)
def notify_user_activation(user, request=None): """Send mail for user activation""" security = query_utility(ISecurityManager) settings = INotificationSettings(security) if not settings.enable_notifications: # pylint: disable=assignment-from-no-return LOGGER.info("Security notifications disabled, no message sent...") return mailer = settings.get_mailer() # pylint: disable=assignment-from-no-return if mailer is None: LOGGER.warning("Can't find mailer utility, no notification message sent!") return if request is None: request = check_request() translate = request.localizer.translate i18n_settings = II18n(settings) message_text, template_name = None, None if user.self_registered: # pylint: disable=assignment-from-no-return message_text = i18n_settings.query_attribute('registration_template', request=request) if not message_text: template_name = 'templates/register-message.pt' elif user.wait_confirmation: # pylint: disable=assignment-from-no-return message_text = i18n_settings.query_attribute('confirmation_template', request=request) if not message_text: template_name = 'templates/register-info.pt' site = get_parent(request.context, ISite) if message_text is not None: message_text = message_text.format(**user.to_dict()) elif template_name is not None: message_text = render(template_name, request=request, value={ 'user': user, 'site': site, 'settings': settings }) html_body = render('templates/register-body.pt', request=request, value={ 'user': user, 'site': site, 'settings': settings, 'message': message_text }) message = Message( subject=translate(_("{prefix}Please confirm registration")).format( prefix="{prefix} ".format(prefix=settings.subject_prefix) if settings.subject_prefix else ''), sender='{name} <{email}>'.format(name=settings.sender_name, email=settings.sender_email), recipients=("{firstname} {lastname} <{email}>".format(firstname=user.firstname, lastname=user.lastname, email=user.email),), html=Attachment(data=html_body, content_type='text/html; charset=utf-8', disposition='inline', transfer_encoding='quoted-printable'), body=Attachment(data=html_to_text(html_body), content_type='text/plain; charset=utf-8', disposition='inline', transfer_encoding='quoted-printable')) mailer.send(message)
class IPlugin(IContained, IAttributeAnnotatable): """Basic authentication plug-in interface""" containers('pyams_security.interfaces.IAuthentication') prefix = TextLine( title=_("Plug-in prefix"), description=_( "This prefix is mainly used by authentication plug-ins to " "mark principals")) title = TextLine(title=_("Plug-in title"), required=False) enabled = Bool(title=_("Enabled plug-in?"), description=_("You can choose to disable any plug-in..."), required=True, default=True)
class IUserRegistrationConfirmationInfo(Interface): """User registration confirmation info""" activation_hash = TextLine(title=_("Activation hash"), required=True) login = TextLine(title=USER_LOGIN_TITLE, required=True) password = EncodedPasswordField(title=_("Password"), min_length=8, required=True) confirmed_password = EncodedPasswordField(title=_("Confirmed password"), required=True) @invariant def check_password(self): """Check for password confirmation""" if self.password != self.confirmed_password: raise Invalid(_("You didn't confirmed your password correctly!")) check_password(self.password)
def check_activation(self, hash, login, password): # pylint: disable=redefined-builtin """Check is given hash is matching stored one, and activate user""" if self.self_registered: # If principal was registered by it's own, we check activation hash # with given login and password manager = get_utility(IPasswordManager, name=self.password_manager) password = manager.encodePassword(password, salt=self._password_salt) secret = hmac.new(password, login.encode(), digestmod=hashlib.sha256) secret.update(self.activation_secret.encode()) activation_hash = base64.b32encode(secret.digest()).decode() if hash != activation_hash: raise Invalid(_("Can't activate profile with given params!")) else: # If principal was registered by a site manager, just check that # hash is matching stored one and update user password... if hash != self.activation_hash: raise Invalid(_("Can't activate profile with given params!")) self.password = password self.wait_confirmation = False self.activation_date = datetime.utcnow() self.activated = True
def check_password(password): """Check validity of a given password""" nbmaj = 0 nbmin = 0 nbn = 0 nbo = 0 for car in password: if ord(car) in MAJS: nbmaj += 1 elif ord(car) in MINS: nbmin += 1 elif ord(car) in NUMS: nbn += 1 else: nbo += 1 if [nbmin, nbmaj, nbn, nbo].count(0) > 1: raise Invalid( _("Your password must contain at least three of these kinds of characters: " "lowercase letters, uppercase letters, numbers and special characters" ))
class ILocalGroup(Interface): """Local principals group interface""" containers(IGroupsFolderPlugin) group_id = TextLine( title=_("Group ID"), description=_("This ID should be unique between all groups"), required=True, readonly=True) title = TextLine(title=_("Title"), description=_("Public label of this group"), required=True) description = Text(title=_("Description"), required=False) principals = PrincipalsSetField( title=_("Group principals"), description=_("IDs of principals contained in this group"), required=False, default=set())
class IAdminAuthenticationPlugin(IAuthenticationPlugin): """Admin authentication plug-in base interface""" login = TextLine(title=_("Admin. login")) password = EncodedPasswordField(title=_("Admin. password"), required=False)
def include_package(config): """Pyramid package include""" # add translations config.add_translation_dirs('pyams_security:locales') config.registry.registerUtility(factory=PlainTextPasswordManager, provided=IPasswordManager, name='Plain Text') config.registry.registerUtility(factory=MD5PasswordManager, provided=IPasswordManager, name='MD5') config.registry.registerUtility(factory=SHA1PasswordManager, provided=IPasswordManager, name='SHA1') config.registry.registerUtility(factory=SSHAPasswordManager, provided=IPasswordManager, name='SSHA') # add configuration directives config.add_directive('register_permission', register_permission) config.add_directive('register_role', register_role) config.add_directive('upgrade_role', upgrade_role) # add request methods config.add_request_method(get_principal, 'principal', reify=True) # add subscribers predicate config.add_subscriber_predicate('role_selector', RoleSelector) config.add_subscriber_predicate('plugin_selector', PluginSelector) # register standard permissions config.register_permission({ 'id': PUBLIC_PERMISSION, 'title': _("View public contents") }) config.register_permission({ 'id': VIEW_PERMISSION, 'title': _("View protected contents") }) config.register_permission({ 'id': MANAGE_PERMISSION, 'title': _("Manage contents properties") }) config.register_permission({ 'id': VIEW_SYSTEM_PERMISSION, 'title': _("View management screens") }) config.register_permission({ 'id': MANAGE_SYSTEM_PERMISSION, 'title': _("Manage system properties") }) config.register_permission({ 'id': MANAGE_SECURITY_PERMISSION, 'title': _("Manage security") }) config.register_permission({ 'id': MANAGE_ROLES_PERMISSION, 'title': _("Manage roles") }) # register standard roles config.register_role({ 'id': SYSTEM_ADMIN_ROLE, 'title': _("System manager (role)"), 'permissions': { PUBLIC_PERMISSION, VIEW_PERMISSION, MANAGE_PERMISSION, MANAGE_SYSTEM_PERMISSION, VIEW_SYSTEM_PERMISSION, MANAGE_SECURITY_PERMISSION, MANAGE_ROLES_PERMISSION }, 'managers': {ADMIN_USER_ID, ROLE_ID.format(SYSTEM_ADMIN_ROLE)} }) config.register_role({ 'id': SYSTEM_VIEWER_ROLE, 'title': _("System viewer (role)"), 'permissions': {PUBLIC_PERMISSION, VIEW_PERMISSION, VIEW_SYSTEM_PERMISSION}, 'managers': {ADMIN_USER_ID, ROLE_ID.format(SYSTEM_ADMIN_ROLE)} }) # custom classes implementations if not asbool( config.registry.settings.get( 'pyams.security.disable-default-policy', False)): classImplements(BaseSiteRoot, IDefaultProtectionPolicy) BaseSiteRoot.__acl__ = ProtectedObjectMixin.__acl__ config.scan()
def check_mailer(self): """Check mailer to enable notifications""" if self.enable_notifications and not self.mailer: raise Invalid( _("Notifications can't be enabled without mailer utility"))
class INotificationSettings(IAttributeAnnotatable): """Mailer notification settings interface""" enable_notifications = Bool( title=_("Enable notifications?"), description=_("If 'no', mail notifications will be disabled"), required=True, default=False) mailer = Choice( title=_("Mailer utility"), description=_("Mail delivery utility used to send notifications"), required=False, vocabulary=MAILERS_VOCABULARY_NAME) @invariant def check_mailer(self): """Check mailer to enable notifications""" if self.enable_notifications and not self.mailer: raise Invalid( _("Notifications can't be enabled without mailer utility")) def get_mailer(self): """Return mailer utility""" service_name = TextLine( title=_("Service name"), description=_("Name of service as defined in registration mail " "subject"), required=True) service_owner = TextLine( title=_("Service owner"), description=_("Name of the entity providing this service, which " "will be visible in notifications messages"), required=True) sender_name = TextLine( title=_("Sender name"), description=_("Visible name of registration mail sender"), required=True) sender_email = TextLine( title=_("Sender email"), description=_("Email address of registration mail sender"), required=True) subject_prefix = TextLine( title=_("Subject prefix"), description=_("This prefix will be inserted into subject prefix of " "each notification message"), required=False) confirmation_template = I18nHTMLField( title=_("Confirmation template"), description= _("This template will be used instead of default template to send " "notification when a user is registered by a system administrator; you can " "use some user properties into the message body, like: {login}, {email}, " "{firstname}, {lastname}, {title} or {company_name}; message activation " "link and footer are added automatically"), required=False) registration_template = I18nHTMLField( title=_("Registration template"), description= _("This template will be used instead of default template to send " "notificaiton when a user is auto-registered; you can use some user " "properties into the message body, like: {login}, {email}, {firstname}, " "{lastname}, {title} or {company_name}; message activation link and footer " "are added automatically"), required=False) signature = Text(title=_("Email signature"), description=_("Text displayed in email footer"), required=False)
class UnknownPrincipal: """Unknown principal info""" id = '__none__' # pylint: disable=invalid-name title = _("< unknown principal >")
class IProtectedObject(IAttributeAnnotatable): """Protected object interface This is the only interface used by authorization policy. So you are free to implement custom protection mechanisms. """ inherit_parent_security = Bool( title=_("Inherit parent security?"), description=_("Get access control entries (ACE) inherited " "from parent levels"), required=True, default=True) everyone_denied = PermissionsSetField( title=_("Public denied permissions"), description=_("These permissions will be denied to all " "users. Denied permissions take " "precedence over granted ones."), required=False) everyone_granted = PermissionsSetField( title=_("Public granted permissions"), description=_("These permissions will be granted to " "all users"), required=False) authenticated_denied = PermissionsSetField( title=_("Authenticated denied permissions"), description=_("These permissions will be denied " "to authenticated users. Denied " "permissions take precedence over " "granted ones."), required=False) authenticated_granted = PermissionsSetField( title=_("Authenticated granted permissions"), description=_("These permissions will be granted " "to authenticated users"), required=False) inherit_parent_roles = Bool( title=_("Inherit parent roles?"), description=_("Get roles granted on parent levels"), required=True, default=True) def __acl__(self): """Object ACL""" def get_principals(self, role_id): """Get ID of principals who were granted given role May return an empty set when empty """ def get_roles(self, principal_id): """Get ID of roles granted to given principal May return an empty set when empty """ def get_roles_ids(self, principal_id): """Get ID of roles granted to given principal""" def get_permissions(self, principal_id): """Get ID of permissions granted to given principal""" def get_everyone_denied(self): """Get denied permissions for everyone, including inherited ones""" def get_everyone_granted(self): """Get granted permissions for everyone, including inherited ones""" def get_authenticated_denied(self): """Get denied permissions for authenticated, including inherited ones""" def get_authenticated_granted(self): """Get granted permissions for authenticated, including inherited ones""" def get_granted_roles(self): """Get all roles, including inherited ones"""
class ISecurityManager(IContainer, IDirectoryPluginInfo, IAttributeAnnotatable): """Authentication and principals management utility""" contains(IPlugin) open_registration = Bool( title=_("Enable free registration?"), description=_("If 'Yes', any use will be able to create a new user " "account"), required=False, default=False) users_folder = Choice( title=_("Users folder"), description=_( "Name of users folder used to store registered principals"), required=False, vocabulary=USERS_FOLDERS_VOCABULARY_NAME) @invariant def check_users_folder(self): """Check for open registration""" if self.open_registration and not self.users_folder: raise Invalid( _("You can't activate open registration without selecting a users " "folder")) credentials_plugins_names = Tuple( title=_("Credentials plug-ins"), description=_("These plug-ins are used to extract " "credentials from an incoming request (the " "Pyramid session plug-in is built-in!). " "They can rely on authentication plug-ins " "to ensure that provided credentials are " "valid..."), value_type=TextLine(), readonly=True) authentication_plugins_names = Tuple( title=_("Authentication plug-ins"), description=_("The plug-ins can be used to check " "extracted credentials against a local or " "remote users database"), value_type=TextLine(), default=()) directory_plugins_names = Tuple( title=_("Directory plug-ins"), description=_("The plug-in can be used to extract principals " "information"), value_type=TextLine(), default=()) def get_plugin(self, name): """Get plug-in matching given name""" credentials_plugins = Attribute( "Iterator on registered credentials plug-ins") authentication_plugins = Attribute( "Iterator on registered and local authentication plug-ins") directory_plugins = Attribute( "Iterator on registered and local directory plug-ins") groups_directory_plugins = Attribute( "Iterator on registered and local groups plug-ins") def effective_principals(self, principal_id, request=None, context=None): """Get effective principals of provided principal ID""" def get_principal(self, principal_id, info=True): """Principal lookup for provided principal ID""" def get_all_principals(self, principal_id): """Get all principals of given principal ID"""
def check_users_folder(self): """Check for open registration""" if self.open_registration and not self.users_folder: raise Invalid( _("You can't activate open registration without selecting a users " "folder"))
# SYSTEM_PREFIX = 'system' ADMIN_USER_NAME = '__system__' ADMIN_USER_LOGIN = '******' ADMIN_USER_ID = '{0}:{1}'.format(SYSTEM_PREFIX, ADMIN_USER_LOGIN) INTERNAL_USER_NAME = '__internal__' INTERNAL_USER_LOGIN = '******' INTERNAL_USER_ID = '{0}:{1}'.format(SYSTEM_PREFIX, INTERNAL_USER_LOGIN) SYSTEM_ADMIN_ROLE = 'system.Manager' SYSTEM_VIEWER_ROLE = 'system.Viewer' USER_LOGIN_TITLE = _("User login") # # Roles events interfaces # class IRoleEvent(IObjectEvent): """Base role event interface""" role_id = Attribute("Modified role ID") principal_id = Attribute("Modified principal ID") @implementer(IRoleEvent)
def check_activated_user(self): """Check for missing password of activated user""" if not self.password and not self.wait_confirmation: raise Invalid( _("You can't activate an account without setting a password!"))
def check_email(self): """Check for invalid email address""" if not EMAIL_REGEX.match(self.email): raise Invalid(_("Given email address is not valid!"))
class ILocalUser(IAttributeAnnotatable): """Local user interface""" containers(IUsersFolderPlugin) login = TextLine(title=USER_LOGIN_TITLE, required=True, readonly=True) @invariant def check_login(self): """Set login as mail when missing""" if not self.login: self.login = self.email email = TextLine(title=_("User email address"), required=True) @invariant def check_email(self): """Check for invalid email address""" if not EMAIL_REGEX.match(self.email): raise Invalid(_("Given email address is not valid!")) firstname = TextLine(title=_("First name"), required=True) lastname = TextLine(title=_("Last name"), required=True) title = Attribute("User full name") company_name = TextLine(title=_("Company name"), required=False) password_manager = Choice( title=_("Password manager name"), description=_("Utility used to encrypt user password"), required=True, vocabulary=PASSWORD_MANAGERS_VOCABULARY_NAME, default='PBKDF2') password = EncodedPasswordField(title=_("Password"), min_length=8, required=False) confirmed_password = EncodedPasswordField(title=_("Confirmed password"), min_length=8, required=False) wait_confirmation = Bool( title=_("Wait confirmation?"), description=_("If 'no', user will be activated immediately without " "waiting email confirmation"), required=True, default=True) @invariant def check_activated_user(self): """Check for missing password of activated user""" if not self.password and not self.wait_confirmation: raise Invalid( _("You can't activate an account without setting a password!")) self_registered = Bool(title=_("Self-registered profile?"), required=True, default=True, readonly=True) activation_secret = TextLine( title=_("Activation secret key"), description=_("This private secret is used to create and check " "activation hash")) activation_hash = TextLine( title=_("Activation hash"), description=_("This hash is provided into activation message URL. " "Activation hash is missing for local users which " "were registered without waiting their " "confirmation.")) activated = Bool(title=_("Activated"), required=True, default=False) activation_date = Datetime(title=_("Activation date"), required=False) def check_password(self, password): """Check user password against provided one""" def generate_secret(self, notify=True, request=None): """Generate secret key of this profile""" def refresh_secret(self, notify=True, request=None): """Refresh secret key of this profile""" def check_activation(self, hash, login, password): # pylint: disable=redefined-builtin """Check activation for given settings""" def to_dict(self): """Get main user properties as mapping"""
def check_password(self): """Check for password confirmation""" if self.password != self.confirmed_password: raise Invalid(_("You didn't confirmed your password correctly!")) check_password(self.password)