コード例 #1
0
def ensureTokenScopes(token, scope):
    """
    Call this to validate a token scope for endpoints that require tokens
    other than a user authentication token. Raises an AccessException if the
    required scopes are not allowed by the given token.

    :param token: The token object used in the request.
    :type token: dict
    :param scope: The required scope or set of scopes.
    :type scope: `str or list of str`
    """
    tokenModel = Token()
    if tokenModel.hasScope(token, TokenScope.USER_AUTH):
        return

    if not tokenModel.hasScope(token, scope):
        setCurrentUser(None)
        if isinstance(scope, six.string_types):
            scope = (scope,)
        raise AccessException(
            'Invalid token scope.\n'
            'Required: %s.\n'
            'Allowed: %s' % (
                ' '.join(scope),
                '' if token is None else ' '.join(tokenModel.getAllowedScopes(token))))
コード例 #2
0
ファイル: rest.py プロジェクト: mtg137/mindlogger-backend
    def _validateCsrfToken(self, state):
        """
        Tests the CSRF token value in the cookie to authenticate the user as
        the originator of the OAuth2 login. Raises a RestException if the token
        is invalid.
        """
        csrfTokenId, _, redirect = state.partition('.')

        token = Token().load(csrfTokenId,
                             objectId=False,
                             level=AccessType.READ)
        if token is None:
            raise RestException('Invalid CSRF token (state="%s").' % state,
                                code=403)

        Token().remove(token)

        if token['expires'] < datetime.datetime.utcnow():
            raise RestException('Expired CSRF token (state="%s").' % state,
                                code=403)

        if not redirect:
            raise RestException('No redirect location (state="%s").' % state)

        return redirect
コード例 #3
0
    def verifyEmail(self, user, token):
        token = Token().load(token,
                             user=user,
                             level=AccessType.ADMIN,
                             objectId=False,
                             exc=True)
        delta = (token['expires'] - datetime.datetime.utcnow()).total_seconds()
        hasScope = Token().hasScope(token, TokenScope.EMAIL_VERIFICATION)

        if token.get('userId') != user['_id'] or delta <= 0 or not hasScope:
            raise AccessException('The token is invalid or expired.')

        user['emailVerified'] = True
        Token().remove(token)
        user = self._model.save(user)

        if self._model.canLogin(user):
            setCurrentUser(user)
            authToken = self.sendAuthTokenCookie(user)
            return {
                'user': self._model.filter(user, user),
                'authToken': {
                    'token': authToken['_id'],
                    'expires': authToken['expires'],
                    'scope': authToken['scope']
                },
                'message': 'Email verification succeeded.'
            }
        else:
            return {
                'user': self._model.filter(user, user),
                'message': 'Email verification succeeded.'
            }
コード例 #4
0
    def checkTemporaryPassword(self, user, token):
        token = Token().load(token,
                             user=user,
                             level=AccessType.ADMIN,
                             objectId=False,
                             exc=True)
        delta = (token['expires'] - datetime.datetime.utcnow()).total_seconds()
        hasScope = Token().hasScope(token, TokenScope.TEMPORARY_USER_AUTH)

        if token.get('userId') != user['_id'] or delta <= 0 or not hasScope:
            raise AccessException(
                'The token does not grant temporary access to this user.')

        # Temp auth is verified, send an actual auth token now. We keep the
        # temp token around since it can still be used on a subsequent request
        # to change the password
        authToken = self.sendAuthTokenCookie(user)

        return {
            'user': self._model.filter(user, user),
            'authToken': {
                'token': authToken['_id'],
                'expires': authToken['expires'],
                'temporary': True
            },
            'message': 'Temporary access token is valid.'
        }
コード例 #5
0
class Token(Resource):
    """API Endpoint for non-user tokens in the system."""
    def __init__(self):
        super(Token, self).__init__()
        self.resourceName = 'token'
        self._model = TokenModel()

        self.route('DELETE', ('session', ), self.deleteSession)
        self.route('GET', ('session', ), self.getSession)
        self.route('GET', ('current', ), self.currentSession)
        self.route('GET', ('scopes', ), self.listScopes)

    @access.public
    @autoDescribeRoute(
        Description('Retrieve the current session information.').responseClass(
            'Token'))
    def currentSession(self):
        return self.getCurrentToken()

    @access.public
    @autoDescribeRoute(
        Description('Get an anonymous session token for the system.').notes(
            'If you are logged in, this will return a token associated with that login.'
        ).responseClass('Token'))
    def getSession(self):
        token = self.getCurrentToken()

        # Only create and send new cookie if token isn't valid or will expire soon
        if not token:
            token = self.sendAuthTokenCookie(
                None, scope=TokenScope.ANONYMOUS_SESSION)

        return {'token': token['_id'], 'expires': token['expires']}

    @access.token
    @autoDescribeRoute(
        Description('Remove a session from the system.').responseClass(
            'Token').notes('Attempts to delete your authentication cookie.'))
    def deleteSession(self):
        token = self.getCurrentToken()
        if token:
            self._model.remove(token)
        self.deleteAuthTokenCookie()
        return {'message': 'Session deleted.'}

    @access.public
    @autoDescribeRoute(
        Description('List all token scopes available in the system.'))
    def listScopes(self):
        return TokenScope.listScopes()
コード例 #6
0
def _authorizeInitUpload(event):
    """
    Called when initializing an upload, prior to the default handler. Checks if
    the user is passing an authorized upload token, and if so, sets the current
    request-thread user to be whoever created the token.
    """
    token = getCurrentToken()
    params = event.info['params']
    tokenModel = Token()
    parentType = params.get('parentType')
    parentId = params.get('parentId', '')
    requiredScopes = {TOKEN_SCOPE_AUTHORIZED_UPLOAD, 'authorized_upload_folder_%s' % parentId}

    if parentType == 'folder' and tokenModel.hasScope(token=token, scope=requiredScopes):
        user = User().load(token['userId'], force=True)
        setCurrentUser(user)
コード例 #7
0
def getCurrentToken(allowCookie=None):
    """
    Returns the current valid token object that was passed via the token header
    or parameter, or None if no valid token was passed.

    :param allowCookie: Normally, authentication via cookie is disallowed to
        protect against CSRF attacks. If you want to expose an endpoint that can
        be authenticated with a token passed in the Cookie, set this to True.
        This should only be used on read-only operations that will not make any
        changes to data on the server, and only in cases where the user agent
        behavior makes passing custom headers infeasible, such as downloading
        data to disk in the browser. In the event that allowCookie is not explicitly
        passed, it will default to False unless the access.cookie decorator is used.
    :type allowCookie: bool
    """
    if allowCookie is None:
        allowCookie = getattr(cherrypy.request, 'girderAllowCookie', False)

    tokenStr = None
    if 'token' in cherrypy.request.params:  # Token as a parameter
        tokenStr = cherrypy.request.params.get('token')
    elif 'Girder-Token' in cherrypy.request.headers:
        tokenStr = cherrypy.request.headers['Girder-Token']
    elif allowCookie and 'girderToken' in cherrypy.request.cookie:
        tokenStr = cherrypy.request.cookie['girderToken'].value

    if not tokenStr:
        return None

    return Token().load(tokenStr, force=True, objectId=False)
コード例 #8
0
ファイル: access.py プロジェクト: mtg137/mindlogger-backend
 def wrapped(*args, **kwargs):
     if not rest.getCurrentToken():
         raise AccessException(
             'You must be logged in or have a valid auth token.')
     if required:
         Token().requireScope(rest.getCurrentToken(), scope)
     return fun(*args, **kwargs)
コード例 #9
0
 def createJobToken(self, job, days=7):
     """
     Create a token that can be used just for the management of an individual
     job, e.g. updating job info, progress, logs, status.
     """
     return Token().createToken(days=days,
                                scope='jobs.job_' + str(job['_id']))
コード例 #10
0
    def validate(self, doc):
        from girderformindlogger.models.token import Token
        from girderformindlogger.models.user import User

        if doc['tokenDuration']:
            doc['tokenDuration'] = float(doc['tokenDuration'])
        else:
            doc['tokenDuration'] = None

        doc['name'] = doc['name'].strip()
        doc['active'] = bool(doc.get('active', True))

        if doc['scope'] is not None:
            if not isinstance(doc['scope'], (list, tuple)):
                raise ValidationException('Scope must be a list, or None.')
            if not doc['scope']:
                raise ValidationException('Custom scope list must not be empty.')

            # Ensure only registered scopes are being set
            admin = User().load(doc['userId'], force=True)['admin']
            scopes = TokenScope.scopeIds(admin)
            unknownScopes = set(doc['scope']) - scopes
            if unknownScopes:
                raise ValidationException('Invalid scopes: %s.' % ','.join(unknownScopes))

        # Deactivating an already existing token
        if '_id' in doc and not doc['active']:
            Token().clearForApiKey(doc)

        return doc
コード例 #11
0
    def createToken(self, key, days=None):
        """
        Create a token using an API key.

        :param key: The API key (the key itself, not the full document).
        :type key: str
        :param days: You may request a token duration up to the token duration
            of the API key itself, or pass None to use the API key duration.
        :type days: float or None
        """
        from girderformindlogger.models.setting import Setting
        from girderformindlogger.models.token import Token
        from girderformindlogger.models.user import User

        apiKey = self.findOne({
            'key': key
        })

        if apiKey is None or not apiKey['active']:
            raise ValidationException('Invalid API key.')

        cap = apiKey['tokenDuration'] or Setting().get(SettingKey.COOKIE_LIFETIME)
        days = min(float(days or cap), cap)

        user = User().load(apiKey['userId'], force=True)

        # Mark last used stamp
        apiKey['lastUse'] = datetime.datetime.utcnow()
        apiKey = self.save(apiKey)
        token = Token().createToken(user=user, days=days, scope=apiKey['scope'], apiKey=apiKey)
        return (user, token)
コード例 #12
0
    def generateTemporaryPassword(self, email):
        user = self._model.findOne({'email': self._model.hash(email.lower()), 'email_encrypted': True})

        if not user:
            user = self._model.findOne({'email': email.lower(), 'email_encrypted': {'$ne': True}})

        if not user:
            raise RestException('That email is not registered.')

        token = Token().createToken(user, days=(15/1440.0), scope=TokenScope.TEMPORARY_USER_AUTH)

        url = '%s#useraccount/%s/token/%s' % (
            mail_utils.getEmailUrlPrefix(), str(user['_id']), str(token['_id']))

        html = mail_utils.renderTemplate('temporaryAccess.mako', {
            'url': url,
            'token': str(token['_id'])
        })

        mail_utils.sendMail(
            '%s: Temporary access' % Setting().get(SettingKey.BRAND_NAME),
            html,
            [email]
        )
        return {'message': 'Sent temporary access email.'}
コード例 #13
0
    def acceptInvitationByToken(self, invitation, lang, email, token):
        """
        Accept an invitation.
        """
        currentUser = Token().load(
            token,
            force=True,
            objectId=False,
            exc=False
        ).get('userId', None)
        if currentUser is not None:
            currentUser = UserModel().load(currentUser, force=True)
        if currentUser is None:
            raise AccessException(
                "Invalid token."
            )
        if invitation.get('role', 'user') == 'owner':
            AppletModel().receiveOwnerShip(AppletModel().load(invitation['appletId'], force=True), currentUser, email)
        else:
            profile = InvitationModel().acceptInvitation(invitation, currentUser, email)

            # editors should be able to access duplicated applets
            if invitation.get('role','user') == 'editor' or invitation.get('role', 'user') == 'manager':
                InvitationModel().accessToDuplicatedApplets(invitation, currentUser, email)

        InvitationModel().remove(invitation)
        return profile
コード例 #14
0
def _uploadComplete(event):
    """
    Called after an upload finishes. We check if our current token is a special
    authorized upload token, and if so, delete it.

    TODO we could alternatively keep a reference count inside each token that authorized
    more than a single upload at a time, and just decrement it here.
    """
    token = getCurrentToken()
    if token and 'authorizedUploadId' in token:
        user = User().load(token['userId'], force=True)
        item = Item().load(event.info['file']['itemId'], force=True)

        # Save the metadata on the item
        item['description'] = token['authorizedUploadDescription']
        item['authorizedUploadEmail'] = token['authorizedUploadEmail']
        Item().save(item)

        text = mail_utils.renderTemplate('authorized_upload.uploadFinished.mako', {
            'itemId': item['_id'],
            'itemName': item['name'],
            'itemDescription': item.get('description', '')
        })
        mail_utils.sendMail('Authorized upload complete', text, [user['email']])
        Token().remove(token)
コード例 #15
0
def _storeUploadId(event):
    """
    Called after an upload is first initialized successfully. Sets the authorized upload ID
    in the token, ensuring it can be used for only this upload.
    """
    returnVal = event.info['returnVal']
    token = getCurrentToken()
    tokenModel = Token()
    isAuthorizedUpload = tokenModel.hasScope(token, TOKEN_SCOPE_AUTHORIZED_UPLOAD)

    if isAuthorizedUpload and returnVal.get('_modelType', 'upload') == 'upload':
        params = event.info['params']
        token['scope'].remove(TOKEN_SCOPE_AUTHORIZED_UPLOAD)
        token['authorizedUploadId'] = returnVal['_id']
        token['authorizedUploadDescription'] = params.get('authorizedUploadDescription', '')
        token['authorizedUploadEmail'] = params.get('authorizedUploadEmail')
        tokenModel.save(token)
コード例 #16
0
    def __init__(self):
        super(Token, self).__init__()
        self.resourceName = 'token'
        self._model = TokenModel()

        self.route('DELETE', ('session', ), self.deleteSession)
        self.route('GET', ('session', ), self.getSession)
        self.route('GET', ('current', ), self.currentSession)
        self.route('GET', ('scopes', ), self.listScopes)
コード例 #17
0
ファイル: user.py プロジェクト: jj105/mindlogger-app-backend
    def _sendVerificationEmail(self, user, email):
        from girderformindlogger.models.token import Token

        token = Token().createToken(user,
                                    days=1,
                                    scope=TokenScope.EMAIL_VERIFICATION)
        url = '%s#useraccount/%s/verification/%s' % (
            mail_utils.getEmailUrlPrefix(), str(user['_id']), str(
                token['_id']))
        text = mail_utils.renderTemplate('emailVerification.mako',
                                         {'url': url})
        mail_utils.sendMail('Girder: Email verification', text, [email])
コード例 #18
0
 def acceptInvitationByToken(self, invitation, token):
     """
     Accept an invitation.
     """
     currentUser = Token().load(token,
                                force=True,
                                objectId=False,
                                exc=False).get('userId')
     if currentUser is not None:
         currentUser = UserModel().load(currentUser, force=True)
     if currentUser is None:
         raise AccessException(
             "You must be logged in to accept an invitation.")
     return (InvitationModel().acceptInvitation(invitation, currentUser))
コード例 #19
0
    def changePassword(self, old, new):
        user = self.getCurrentUser()
        token = None

        if not old:
            raise RestException('Old password must not be empty.')

        if (not self._model.hasPassword(user)
                or not self._model._cryptContext.verify(old, user['salt'])):
            # If not the user's actual password, check for temp access token
            token = Token().load(old, force=True, objectId=False, exc=False)
            if (not token or not token.get('userId')
                    or token['userId'] != user['_id'] or not Token().hasScope(
                        token, TokenScope.TEMPORARY_USER_AUTH)):
                raise AccessException('Old password is incorrect.')

        self._model.setPassword(user, new)

        if token:
            # Remove the temporary access token if one was used
            Token().remove(token)

        return {'message': 'Password changed.'}
コード例 #20
0
 def acceptInvitationByToken(self, invitation, email, token):
     """
     Accept an invitation.
     """
     currentUser = Token().load(token,
                                force=True,
                                objectId=False,
                                exc=False).get('userId', None)
     if currentUser is not None:
         currentUser = UserModel().load(currentUser, force=True)
     if currentUser is None:
         raise AccessException("Invalid token.")
     return (InvitationModel().acceptInvitation(invitation, currentUser,
                                                email))
コード例 #21
0
    def createAuthorizedUpload(self, folder, params):
        try:
            if params.get('duration'):
                days = int(params.get('duration'))
            else:
                days = Setting().get(SettingKey.COOKIE_LIFETIME)
        except ValueError:
            raise ValidationException(
                'Token duration must be an integer, or leave it empty.')

        token = Token().createToken(
            days=days,
            user=self.getCurrentUser(),
            scope=(TOKEN_SCOPE_AUTHORIZED_UPLOAD,
                   'authorized_upload_folder_%s' % folder['_id']))

        url = '%s#authorized_upload/%s/%s' % (mail_utils.getEmailUrlPrefix(),
                                              folder['_id'], token['_id'])

        return {'url': url}
コード例 #22
0
def buildHeaders(headers, cookie, user, token, basicAuth, authHeader):
    from girderformindlogger.models.token import Token

    headers = headers[:]
    if cookie is not None:
        headers.append(('Cookie', cookie))

    if user is not None:
        token = Token().createToken(user)
        headers.append(('Girder-Token', str(token['_id'])))
    elif token is not None:
        if isinstance(token, dict):
            headers.append(('Girder-Token', token['_id']))
        else:
            headers.append(('Girder-Token', token))

    if basicAuth is not None:
        auth = base64.b64encode(basicAuth.encode('utf8'))
        headers.append((authHeader, 'Basic %s' % auth.decode()))

    return headers
コード例 #23
0
    def sendAuthTokenCookie(self, user=None, scope=None, token=None, days=None):
        """
        Helper method to send the authentication cookie
        """
        if days is None:
            days = float(Setting().get(SettingKey.COOKIE_LIFETIME))

        if token is None:
            token = Token().createToken(user, days=days, scope=scope)

        cookie = cherrypy.response.cookie
        cookie['girderToken'] = str(token['_id'])
        cookie['girderToken']['path'] = '/'
        cookie['girderToken']['expires'] = int(days * 3600 * 24)

        # CherryPy proxy tools modify the request.base, but not request.scheme, when receiving
        # X-Forwarded-Proto headers from a reverse proxy
        if cherrypy.request.scheme == 'https' or cherrypy.request.base.startswith('https'):
            cookie['girderToken']['secure'] = True

        return token
コード例 #24
0
ファイル: user.py プロジェクト: jj105/mindlogger-app-backend
    def remove(self, user, progress=None, **kwargs):
        """
        Delete a user, and all references to it in the database.

        :param user: The user document to delete.
        :type user: dict
        :param progress: A progress context to record progress on.
        :type progress: girderformindlogger.utility.progress.ProgressContext or None.
        """
        from girderformindlogger.models.folder import Folder
        from girderformindlogger.models.group import Group
        from girderformindlogger.models.token import Token

        # Delete all authentication tokens owned by this user
        Token().removeWithQuery({'userId': user['_id']})

        # Delete all pending group invites for this user
        Group().update({'requests': user['_id']},
                       {'$pull': {
                           'requests': user['_id']
                       }})

        # Delete all of the folders under this user
        folderModel = Folder()
        folders = folderModel.find({
            'parentId': user['_id'],
            'parentCollection': 'user'
        })
        for folder in folders:
            folderModel.remove(folder, progress=progress, **kwargs)

        # Finally, delete the user document itself
        AccessControlledModel.remove(self, user)
        if progress:
            progress.update(increment=1,
                            message='Deleted user ' + user['login'])
コード例 #25
0
 def remove(self, doc):
     # Clear tokens corresponding to this API key.
     from girderformindlogger.models.token import Token
     Token().clearForApiKey(doc)
     super(ApiKey, self).remove(doc)
コード例 #26
0
 def logout(self):
     token = self.getCurrentToken()
     if token:
         Token().remove(token)
     self.deleteAuthTokenCookie()
     return {'message': 'Logged out.'}
コード例 #27
0
    def testAuthorizedUpload(self):
        Setting().set(SettingKey.UPLOAD_MINIMUM_CHUNK_SIZE, 1)

        # Anon access should not work
        resp = self.request('/authorized_upload',
                            method='POST',
                            params={'folderId': self.privateFolder['_id']})
        self.assertStatus(resp, 401)

        # Create our secure URL
        resp = self.request('/authorized_upload',
                            method='POST',
                            user=self.admin,
                            params={'folderId': self.privateFolder['_id']})
        self.assertStatusOk(resp)
        parts = resp.json['url'].rsplit('/', 3)
        tokenId, folderId = parts[-1], parts[-2]

        token = Token().load(tokenId, force=True, objectId=False)

        self.assertIsNotNone(token)
        self.assertEqual(folderId, str(self.privateFolder['_id']))
        self.assertEqual(
            set(token['scope']), {
                TOKEN_SCOPE_AUTHORIZED_UPLOAD,
                'authorized_upload_folder_%s' % self.privateFolder['_id']
            })

        # Make sure this token doesn't let us upload into a different folder
        params = {
            'parentType': 'folder',
            'parentId': self.publicFolder['_id'],
            'name': 'hello.txt',
            'size': 11,
            'mimeType': 'text/plain'
        }

        resp = self.request(path='/file',
                            method='POST',
                            params=params,
                            token=tokenId)
        self.assertStatus(resp, 401)

        # Initialize upload into correct folder
        params['parentId'] = self.privateFolder['_id']
        resp = self.request(path='/file',
                            method='POST',
                            params=params,
                            token=tokenId)
        self.assertStatusOk(resp)

        # We should remove the scope that allows further uploads
        upload = Upload().load(resp.json['_id'])
        token = Token().load(tokenId, force=True, objectId=False)
        self.assertEqual(
            token['scope'],
            ['authorized_upload_folder_%s' % self.privateFolder['_id']])

        # Authorized upload ID should be present in the token
        self.assertEqual(token['authorizedUploadId'], upload['_id'])

        # Attempting to initialize new uploads using the token should fail
        resp = self.request(path='/file',
                            method='POST',
                            params=params,
                            token=tokenId)
        self.assertStatus(resp, 401)

        # Uploading a chunk should work with the token
        resp = self.request(path='/file/chunk',
                            method='POST',
                            token=tokenId,
                            body='hello ',
                            params={'uploadId': str(upload['_id'])},
                            type='text/plain')
        self.assertStatusOk(resp)

        # Requesting our offset should work with the token
        # The offset should not have changed
        resp = self.request(path='/file/offset',
                            method='GET',
                            token=tokenId,
                            params={'uploadId': upload['_id']})
        self.assertStatusOk(resp)
        self.assertEqual(resp.json['offset'], 6)

        # Upload the second chunk
        resp = self.request(path='/file/chunk',
                            method='POST',
                            token=tokenId,
                            body='world',
                            params={
                                'offset': 6,
                                'uploadId': str(upload['_id'])
                            },
                            type='text/plain')
        self.assertStatusOk(resp)

        # Trying to upload more chunks should fail
        resp = self.request(path='/file/chunk',
                            method='POST',
                            token=tokenId,
                            body='extra',
                            params={
                                'offset': 11,
                                'uploadId': str(upload['_id'])
                            },
                            type='text/plain')
        self.assertStatus(resp, 401)

        # The token should be destroyed
        self.assertIsNone(Token().load(tokenId, force=True, objectId=False))
コード例 #28
0
ファイル: rest.py プロジェクト: mtg137/mindlogger-backend
    def _createStateToken(self, redirect):
        csrfToken = Token().createToken(days=0.25)

        # The delimiter is arbitrary, but a dot doesn't need to be URL-encoded
        state = '%s.%s' % (csrfToken['_id'], redirect)
        return state