示例#1
0
    def __init__(self):
        self.associations = OOBTree()
        self.handles = OOBTree()
        self.nonces = OITreeSet()

        self.noncetimeline = PersistentList()
        self.assoctimeline = PersistentList()
示例#2
0
    def __init__(self, id, title=None):
        self._setId(id)
        self.title = title

        self.idp_url = None
        self.sp_url = None
        self.signing_cert = None
        self.issuer_id = None
        self.clock_skew = 60
        self.authn_context = AUTHN_CONTEXT
        self.internal_network = ''
        self.internal_authn_context = INTERNAL_AUTHN_CONTEXT

        self._roles = ()
        self._logins = OITreeSet()
示例#3
0
    def __init__(self):
        self.associations=OOBTree()
        self.handles=OOBTree()
        self.nonces=OITreeSet()

        self.noncetimeline=PersistentList()
        self.assoctimeline=PersistentList()
示例#4
0
    def __init__(self, id, title=None):
        self._setId(id)

        self.title = title
        self._roles = ()
        self.client_id = None
        self.client_secret = None
        self.scope = u'openid email profile'
        self.sign_algorithm = u'RS256'
        self.username_attribute = u'sub'
        self.authentication_endpoint = None
        self.token_endpoint = None
        self.user_endpoint = None
        self.jwks_endpoint = None
        self._auto_provisioning_enabled = True
        self.properties_mapping = json.dumps({
            "userid": "sub",
            "fullname": "name",
            "email": "email"
        })
        self.logins = OITreeSet()
示例#5
0
class ZopeStore(OpenIDStore):
    """Zope OpenID store.

    This class implements an OpenID store which uses the ZODB.
    """
    def __init__(self):
        self.associations=OOBTree()
        self.handles=OOBTree()
        self.nonces=OITreeSet()

        self.noncetimeline=PersistentList()
        self.assoctimeline=PersistentList()


    def getAssociationKey(self, server_url, handle):
        """Generate a key used to identify an association in our storage.
        """
        if handle is None:
            return self.handles[server_url][0]

        return (server_url, handle)


    def storeAssociation(self, server_url, association):
        key=self.getAssociationKey(server_url, association.handle)
        self.associations[key]=association.serialize()

        now=time.time()
        def getKey(item):
            return self.getAssociation(item[0], item[1], remove=False).getExpiresIn(now)

        lst=self.handles.get(server_url, [])
        lst.append(key)
        lst.sort(key=getKey)
        self.handles[server_url]=lst
        self.assoctimeline.append((association.issued+association.lifetime, key))


    def getAssociation(self, server_url, handle=None, remove=True):
        try:
            key=self.getAssociationKey(server_url, handle)
            assoc=Association.deserialize(self.associations[key])
        except KeyError:
            return None

        if remove and assoc.getExpiresIn()==0:
            self.removeAssociation(server_url, handle)
            return None

        return assoc


    def removeAssociation(self, server_url, handle):
        key=self.getAssociationKey(server_url, handle)
        try:
            assoc=Association.deserialize(self.associations[key])
            del self.associations[key]

            lst=self.handles[server_url]
            lst.remove(key)
            self.handles[server_url]=lst

            self.assoctimeline.remove((assoc.issued+assoc.lifetime, key))
            return True
        except KeyError:
            return False


    def useNonce(self, server_url, timestamp, salt):
        nonce = (salt, server_url)
        if nonce in self.nonces:
            return False

        self.nonces.insert(nonce)

        if not hasattr(self, "noncetimeline"):
            # BBB for store instances from before 1.0b2
            self.noncetimeline=PersistentList()
        self.noncetimeline.append((timestamp, nonce))

        return True


    def cleanupNonces(self):
        if not hasattr(self, "noncetimeline"):
            return 0

        cutoff=time.time()+SKEW
        count=0
        for (timestamp,nonce) in self.noncetimeline:
            if timestamp<cutoff:
                self.noncetimeline.remove((timestamp,nonce))
                self.nonces.remove(nonce)
                count+=1

        return count


    def cleanupAssociations(self):
        if not hasattr(self, "assoctimeline"):
            return 0

        now=time.time()
        count=0

        expired=(key for (timestamp,key) in self.assoctimeline
                if timestamp<=now)
        for key in expired:
            self.removeAssociation(*key)
            count+=1

        return count
示例#6
0
class ZopeStore(OpenIDStore):
    """Zope OpenID store.

    This class implements an OpenID store which uses the ZODB.
    """
    def __init__(self):
        self.associations = OOBTree()
        self.handles = OOBTree()
        self.nonces = OITreeSet()

        self.noncetimeline = PersistentList()
        self.assoctimeline = PersistentList()

    def getAssociationKey(self, server_url, handle):
        """Generate a key used to identify an association in our storage.
        """
        if handle is None:
            return self.handles[server_url][0]

        return (server_url, handle)

    def storeAssociation(self, server_url, association):
        key = self.getAssociationKey(server_url, association.handle)
        self.associations[key] = association.serialize()

        now = time.time()

        def getKey(item):
            return self.getAssociation(item[0], item[1],
                                       remove=False).getExpiresIn(now)

        lst = self.handles.get(server_url, [])
        lst.append(key)
        lst.sort(key=getKey)
        self.handles[server_url] = lst

        if not hasattr(self, "assoctimeline"):
            # BBB for versions < 1.0b2
            self.assoctimeline = PersistentList()

        self.assoctimeline.append(
            (association.issued + association.lifetime, key))

    def getAssociation(self, server_url, handle=None, remove=True):
        try:
            key = self.getAssociationKey(server_url, handle)
            assoc = Association.deserialize(self.associations[key])
        except KeyError:
            return None

        if remove and assoc.getExpiresIn() == 0:
            self.removeAssociation(server_url, handle)
            return None

        return assoc

    def removeAssociation(self, server_url, handle):
        key = self.getAssociationKey(server_url, handle)
        try:
            assoc = Association.deserialize(self.associations[key])
            del self.associations[key]

            lst = self.handles[server_url]
            lst.remove(key)
            self.handles[server_url] = lst

            self.assoctimeline.remove((assoc.issued + assoc.lifetime, key))
            return True
        except KeyError:
            return False

    def useNonce(self, server_url, timestamp, salt):
        nonce = (salt, server_url)
        if nonce in self.nonces:
            return False

        self.nonces.insert(nonce)

        if not hasattr(self, "noncetimeline"):
            # BBB for store instances from before 1.0b2
            self.noncetimeline = PersistentList()
        self.noncetimeline.append((timestamp, nonce))

        return True

    def cleanupNonces(self):
        if not hasattr(self, "noncetimeline"):
            return 0

        cutoff = time.time() + SKEW
        count = 0
        for (timestamp, nonce) in self.noncetimeline:
            if timestamp < cutoff:
                self.noncetimeline.remove((timestamp, nonce))
                self.nonces.remove(nonce)
                count += 1

        return count

    def cleanupAssociations(self):
        if not hasattr(self, "assoctimeline"):
            return 0

        now = time.time()
        count = 0

        expired = (key for (timestamp, key) in self.assoctimeline
                   if timestamp <= now)
        for key in expired:
            self.removeAssociation(*key)
            count += 1

        return count
示例#7
0
 def setUp(self):
     self.t = OITreeSet()
示例#8
0
class Saml2WebSSOPlugin(BasePlugin):
    """Saml2 Web SSO authentication plugin using HTTP POST binding.
    """
    implements(IAuthenticationPlugin, IExtractionPlugin, IRolesPlugin,
               IUserEnumerationPlugin)

    meta_type = "Saml2 Web SSO plugin"
    security = ClassSecurityInfo()

    # ZMI tab for configuration page
    manage_options = (({
        'label': 'Configuration',
        'action': 'manage_config'
    }, ) + BasePlugin.manage_options + Cacheable.manage_options)

    security.declareProtected(ManagePortal, 'manage_config')
    manage_config = PageTemplateFile('www/config',
                                     globals(),
                                     __name__='manage_config')

    def __init__(self, id, title=None):
        self._setId(id)
        self.title = title

        self.idp_url = None
        self.sp_url = None
        self.signing_cert = None
        self.issuer_id = None
        self.clock_skew = 60
        self.authn_context = AUTHN_CONTEXT
        self.internal_network = ''
        self.internal_authn_context = INTERNAL_AUTHN_CONTEXT

        self._roles = ()
        self._logins = OITreeSet()

    # IExtractionPlugin implementation
    def extractCredentials(self, request):

        creds = {}

        if 'SAMLResponse' in request.form:
            doc = request.form['SAMLResponse']
            try:
                doc = base64.b64decode(doc)
            except TypeError:
                return {}

            try:
                resp = CreateFromDocument(
                    doc,
                    context=self._signature_context(),
                )
            except VerifyError, e:
                logger.warning('Signature verification error: %s' % str(e))
                return {}

            if not resp.Destination.startswith(self.sp_url):
                logger.warning('Wrong destination in SAML response.')
                return {}

            status = resp.Status.StatusCode.Value
            if status != STATUS_SUCCESS:
                # Status code may contain a second-level status code.
                if resp.Status.StatusCode.StatusCode:
                    status += ': ' + resp.Status.StatusCode.StatusCode.Value

                logger.warning('Failed SAML2 request with status code: %s.' %
                               status)
                return {}

            # Verfiy issue time of response.
            now = datetime.utcnow()
            issue_instant = resp.IssueInstant.astimezone(tz=pytz.utc).replace(
                tzinfo=None)
            delta = timedelta(seconds=self.clock_skew)
            if (now + delta) < issue_instant or (now - delta) > issue_instant:
                logger.warning('Clock skew too great.')
                return {}

            # We expect the subject and attributes in the first assertion
            if len(resp.Assertion) > 0:
                assertion = resp.Assertion[0]
                subject = assertion.Subject.NameID.value().encode('utf8')
                attributes = self._extract_attributes(assertion)
                creds['subject'] = subject
                creds['attributes'] = attributes
                self._logins.insert(subject)
            else:
                logger.warning('Missing assertion')
                return {}

        return creds
示例#9
0
 def __init__(self, id, title=None):
     self._setId(id)
     self.title = title
     self._roles = ()
     self._logins = OITreeSet()
示例#10
0
class SAML2Plugin(BasePlugin):
    """SAML2 plugin.
    """
    implements(IRolesPlugin, IUserEnumerationPlugin)

    meta_type = "collective.saml2auth plugin"
    security = ClassSecurityInfo()

    # ZMI tab for configuration page
    manage_options = ((
        {
            'label': 'Configuration',
            'action': 'manage_config'
        },
        {
            'label': 'Users',
            'action': 'manage_users'
        },
    ) + BasePlugin.manage_options + Cacheable.manage_options)

    security.declareProtected(ManagePortal, 'manage_config')
    manage_config = PageTemplateFile('www/config',
                                     globals(),
                                     __name__='manage_config')

    security.declareProtected(ManageUsers, 'manage_users')
    manage_users = PageTemplateFile('www/manage_users',
                                    globals(),
                                    __name__='manage_users')

    def __init__(self, id, title=None):
        self._setId(id)
        self.title = title
        self._roles = ()
        self._logins = OITreeSet()

    def addUser(self, userid):
        if userid in self._logins:
            return

        self._logins.insert(userid)

    def removeUser(self, userid):
        if userid not in self._logins:
            return

        self._logins.remove(userid)

    def listUserInfo(self):
        """ -> ( {}, ...{} )

        o Return one mapping per user, with the following keys:

          - 'user_id'
          - 'login_name'
        """
        return [{'user_id': x, 'login_name': x} for x in self._logins]

    security.declareProtected(ManageUsers, 'manage_addUser')

    @csrf_only
    @postonly
    def manage_addUser(self, user_id, RESPONSE=None, REQUEST=None):
        """ Add a user via the ZMI.
        """
        self.addUser(user_id)

        message = 'User+added'

        if RESPONSE is not None:
            RESPONSE.redirect('{}/manage_users?manage_tabs_message={}'.format(
                self.absolute_url(), message))

    security.declareProtected(ManageUsers, 'manage_removeUsers')

    @csrf_only
    @postonly
    def manage_removeUsers(self, user_ids, RESPONSE=None, REQUEST=None):
        """ Remove one or more users via the ZMI.
        """
        user_ids = filter(None, user_ids)

        if not user_ids:
            message = 'no+users+selected'

        else:
            for user_id in user_ids:
                self.removeUser(user_id)

            message = 'Users+removed'

        if RESPONSE is not None:
            RESPONSE.redirect('{}/manage_users?manage_tabs_message={}'.format(
                self.absolute_url(), message))

    # IUserEnumerationPlugin implementation
    def enumerateUsers(self,
                       id=None,
                       login=None,
                       exact_match=False,
                       sort_by=None,
                       max_results=None,
                       **kw):

        key = id and id or login
        user_infos = []
        pluginid = self.getId()

        # We do not provide search for additional keywords
        if kw:
            return ()

        if not key:
            # Return all users
            for login in self._logins:
                user_infos.append({
                    "id": login,
                    "login": login,
                    "pluginid": pluginid,
                })
        elif key in self._logins:
            # User does exists
            user_infos.append({
                "id": key,
                "login": key,
                "pluginid": pluginid,
            })
        else:
            # User does not exists
            return ()

        if max_results is not None and max_results >= 0:
            user_infos = user_infos[:max_results]

        return tuple(user_infos)

    # IRolesPlugin
    def getRolesForPrincipal(self, principal, request=None):
        # Return a list of roles for the given principal (a user or group).
        if principal.getId() in self._logins:
            return self._roles

        return ()

    security.declareProtected(ManagePortal, 'manage_updateConfig')

    @postonly
    def manage_updateConfig(self, REQUEST):
        """Update configuration of SAML2 plugin.
        """
        response = REQUEST.response

        roles = REQUEST.form.get('roles')
        self._roles = tuple([role.strip() for role in roles.split(',')])

        response.redirect('%s/manage_config?manage_tabs_message=%s' %
                          (self.absolute_url(), 'Configuration+updated.'))

    def roles(self):
        """Accessor for config form"""
        return ','.join(self._roles)
示例#11
0
class OIDCPlugin(BasePlugin):
    """OIDC authentication plugin.
    """
    implements(IRolesPlugin, IUserEnumerationPlugin, IChallengePlugin)

    meta_type = "ftw.oidcauth plugin"
    security = ClassSecurityInfo()

    # ZMI tab for configuration page
    manage_options = ((
        {
            'label': 'Configuration',
            'action': 'manage_config'
        },
        {
            'label': 'Users',
            'action': 'manage_users'
        },
    ) + BasePlugin.manage_options + Cacheable.manage_options)

    security.declareProtected(ManagePortal, 'manage_config')
    manage_config = PageTemplateFile('www/config',
                                     globals(),
                                     __name__='manage_config')

    security.declareProtected(ManageUsers, 'manage_users')
    manage_users = PageTemplateFile('www/manage_users',
                                    globals(),
                                    __name__='manage_users')

    def __init__(self, id, title=None):
        self._setId(id)

        self.title = title
        self._roles = ()
        self.client_id = None
        self.client_secret = None
        self.scope = u'openid email profile'
        self.sign_algorithm = u'RS256'
        self.username_attribute = u'sub'
        self.authentication_endpoint = None
        self.token_endpoint = None
        self.user_endpoint = None
        self.jwks_endpoint = None
        self._auto_provisioning_enabled = True
        self.properties_mapping = json.dumps({
            "userid": "sub",
            "fullname": "name",
            "email": "email"
        })
        self.logins = OITreeSet()

    security.declarePrivate('challenge')

    # Initiate a challenge to the user to provide credentials.
    def challenge(self, request, response, **kw):
        request.response.setCookie('oidc_next', request['ACTUAL_URL'])
        uri = '{}?response_type=code&scope={}&client_id={}&redirect_uri={}'.format(
            self.authentication_endpoint, self.scope, self.client_id,
            get_oidc_request_url(quote_=True))
        response.redirect(uri, lock=True, status=302)
        return True

    def addUser(self, userid):
        if userid in self.logins:
            return

        self.logins.insert(userid)

    def removeUser(self, userid):
        if userid not in self.logins:
            return

        self.logins.remove(userid)

    def listUserInfo(self):
        """ -> ( {}, ...{} )

        o Return one mapping per user, with the following keys:

          - 'user_id'
          - 'login_name'
        """
        return [{'user_id': x, 'login_name': x} for x in self.logins]

    security.declareProtected(ManageUsers, 'manage_addUser')

    @csrf_only
    @postonly
    def manage_addUser(self, user_id, RESPONSE=None, REQUEST=None):
        """ Add a user via the ZMI.
        """
        self.addUser(user_id)

        message = 'User+added'

        if RESPONSE is not None:
            RESPONSE.redirect('{}/manage_users?manage_tabs_message={}'.format(
                self.absolute_url(), message))

    security.declareProtected(ManageUsers, 'manage_removeUsers')

    @csrf_only
    @postonly
    def manage_removeUsers(self, user_ids, RESPONSE=None, REQUEST=None):
        """ Remove one or more users via the ZMI.
        """
        user_ids = filter(None, user_ids)

        if not user_ids:
            message = 'no+users+selected'

        else:
            for user_id in user_ids:
                self.removeUser(user_id)

            message = 'Users+removed'

        if RESPONSE is not None:
            RESPONSE.redirect('{}/manage_users?manage_tabs_message={}'.format(
                self.absolute_url(), message))

    # IUserEnumerationPlugin implementation
    def enumerateUsers(self,
                       id=None,
                       login=None,
                       exact_match=False,
                       sort_by=None,
                       max_results=None,
                       **kw):

        key = id and id or login
        user_infos = []
        pluginid = self.getId()

        # We do not provide search for additional keywords
        if kw:
            return ()

        if not key:
            # Return all users
            for login in self.logins:
                user_infos.append({
                    "id": login,
                    "login": login,
                    "pluginid": pluginid,
                })
        elif key in self.logins:
            # User does exists
            user_infos.append({
                "id": key,
                "login": key,
                "pluginid": pluginid,
            })
        else:
            # User does not exists
            return ()

        if max_results is not None and max_results >= 0:
            user_infos = user_infos[:max_results]

        return tuple(user_infos)

    # IRolesPlugin
    def getRolesForPrincipal(self, principal, request=None):
        # Return a list of roles for the given principal (a user or group).
        if principal.getId() in self.logins:
            return self._roles

        return ()

    security.declareProtected(ManagePortal, 'manage_updateConfig')

    @postonly
    def manage_updateConfig(self, REQUEST):
        """Update configuration of OIDC plugin.
        """
        response = REQUEST.response

        self.client_id = REQUEST.form.get('client-id')
        self.client_secret = REQUEST.form.get('client-secret')
        self.scope = REQUEST.form.get('scope')
        self.sign_algorithm = REQUEST.form.get('sign-algorithm')
        self.authentication_endpoint = REQUEST.form.get(
            'authentication-endpoint')
        self.token_endpoint = REQUEST.form.get('token-endpoint')
        self.user_endpoint = REQUEST.form.get('user-endpoint')
        self.jwks_endpoint = REQUEST.form.get('jwks-endpoint')
        self._auto_provisioning_enabled = REQUEST.form.get(
            'auto-provisioning-enabled')

        roles = REQUEST.form.get('roles')
        self._roles = tuple(
            [role.strip() for role in roles.split(',') if role])

        # only update props if json is valid
        props = REQUEST.form.get('properties-mapping')
        props_data = self.get_valid_json(props)
        if not props_data:
            response.redirect(
                '%s/manage_config?manage_tabs_message=%s' %
                (self.absolute_url(), 'Please make sure the json is valid!'))
            return
        self.properties_mapping = props_data

        response.redirect('%s/manage_config?manage_tabs_message=%s' %
                          (self.absolute_url(), 'Configuration+updated.'))

    def auto_provisioning_enabled(self):
        return True if self._auto_provisioning_enabled else False

    def roles(self):
        return ','.join(self._roles)

    @staticmethod
    def get_valid_json(props):
        try:
            data = ast.literal_eval(props)
        except ValueError as e:
            return False
        return data