Beispiel #1
0
    def test_no_key_wrapping(self):
        data = jwe.encrypt(b"Just Some Data", jwe.kdf(b"key", b"Salt")).split(b".")
        data[1] = b"cmFwcGE="

        with pytest.raises(jwe.exceptions.UnsupportedOption) as e:
            jwe.decrypt(b".".join(data), jwe.kdf(b"key", b"Salt"))

        assert e.value.args[0] == "Key wrapping is currently not supported"
Beispiel #2
0
    def test_improper_key(self):
        key = jwe.kdf(b"Testing", b"Pepper")
        data = b"Just some data"
        encrypted = jwe.encrypt(data, key)

        with pytest.raises(InvalidTag):
            # TODO make this a custom exception
            jwe.decrypt(encrypted, jwe.kdf(b"somekey", b"Salt")) == data
Beispiel #3
0
    def test_improper_key(self):
        key = jwe.kdf(b'Testing', b'Pepper')
        data = b'Just some data'
        encrypted = jwe.encrypt(data, key)

        with pytest.raises(InvalidTag):
            # TODO make this a custom exception
            jwe.decrypt(encrypted, jwe.kdf(b'somekey', b'Salt')) == data
Beispiel #4
0
    def test_invalid_header_json(self):
        with pytest.raises(jwe.exceptions.MalformedData) as e:
            jwe.decrypt(
                jwe.encrypt(b"Just Some Data", jwe.kdf(b"key", b"Salt"))[3:],  # Cut out some of the JSON
                jwe.kdf(b"key", b"Salt"),
            )

        assert e.value.args[0] == "Header is not valid JSON"
Beispiel #5
0
    def test_no_key_wrapping(self):
        data = jwe.encrypt(b'Just Some Data', jwe.kdf(b'key',
                                                      b'Salt')).split(b'.')
        data[1] = b'cmFwcGE='

        with pytest.raises(jwe.exceptions.UnsupportedOption) as e:
            jwe.decrypt(b'.'.join(data), jwe.kdf(b'key', b'Salt'))

        assert e.value.args[0] == 'Key wrapping is currently not supported'
Beispiel #6
0
    def test_invalid_header_json(self):
        with pytest.raises(jwe.exceptions.MalformedData) as e:
            jwe.decrypt(
                jwe.encrypt(b'Just Some Data',
                            jwe.kdf(b'key',
                                    b'Salt'))[3:],  # Cut out some of the JSON
                jwe.kdf(b'key', b'Salt'))

        assert e.value.args[0] == 'Header is not valid JSON'
Beispiel #7
0
    def test_encrypt_decrypt(self):
        key = jwe.kdf(b'Testing', b'Pepper')
        data = b'Just some data'
        encrypted = jwe.encrypt(data, key)

        assert encrypted != data
        assert jwe.decrypt(encrypted, key) == data
Beispiel #8
0
    async def make_request(self, params, headers, cookies):
        try:
            # Note: with simple request whose response is handled right afterwards without "being passed
            #       further along", use the context manager so WB doesn't need to handle the sessions.
            async with aiohttp.request(
                    'get',
                    settings.API_URL,
                    params=params,
                    headers=headers,
                    cookies=cookies,
            ) as response:
                if response.status != 200:
                    try:
                        data = await response.json()
                    except (ValueError, ContentTypeError):
                        data = await response.read()
                    raise exceptions.AuthError(data, code=response.status)

                try:
                    raw = await response.json()
                    signed_jwt = jwe.decrypt(raw['payload'].encode(), JWE_KEY)
                    data = jwt.decode(signed_jwt,
                                      settings.JWT_SECRET,
                                      algorithm=settings.JWT_ALGORITHM,
                                      options={'require_exp': True})
                    return data['data']
                except (jwt.InvalidTokenError, KeyError):
                    raise exceptions.AuthError(data, code=response.status)
        except ClientError:
            raise exceptions.AuthError('Unable to connect to auth sever',
                                       code=503)
Beispiel #9
0
    def make_request(self, params, headers, cookies):
        try:
            response = yield from aiohttp.request(
                'get',
                settings.API_URL,
                params=params,
                headers=headers,
                cookies=cookies,
            )
        except aiohttp.errors.ClientError:
            raise exceptions.AuthError('Unable to connect to auth sever', code=503)

        if response.status != 200:
            try:
                data = yield from response.json()
            except ValueError:
                data = yield from response.read()
            raise exceptions.AuthError(data, code=response.status)

        try:
            raw = yield from response.json()
            signed_jwt = jwe.decrypt(raw['payload'].encode(), JWE_KEY)
            data = jwt.decode(signed_jwt, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM, options={'require_exp': True})
            return data['data']
        except (jwt.InvalidTokenError, KeyError):
            raise exceptions.AuthError(data, code=response.status)
Beispiel #10
0
    def to_python(self, output_json):
        if not output_json:
            return None

        output_json = json.loads(jwe.decrypt(bytes(output_json[len(self.prefix):]), settings.SENSITIVE_DATA_KEY).decode('utf-8'))

        return output_json
Beispiel #11
0
    def test_encrypt_decrypt(self):
        key = jwe.kdf(b"Testing", b"Pepper")
        data = b"Just some data"
        encrypted = jwe.encrypt(data, key)

        assert encrypted != data
        assert jwe.decrypt(encrypted, key) == data
Beispiel #12
0
    async def make_request(self, params, headers, cookies):
        try:
            response = await aiohttp.request(
                'get',
                settings.API_URL,
                params=params,
                headers=headers,
                cookies=cookies,
            )
        except aiohttp.errors.ClientError:
            raise exceptions.AuthError('Unable to connect to auth sever',
                                       code=503)

        if response.status != 200:
            try:
                data = await response.json()
            except ValueError:
                data = await response.read()
            raise exceptions.AuthError(data, code=response.status)

        try:
            raw = await response.json()
            signed_jwt = jwe.decrypt(raw['payload'].encode(), JWE_KEY)
            data = jwt.decode(signed_jwt,
                              settings.JWT_SECRET,
                              algorithm=settings.JWT_ALGORITHM,
                              options={'require_exp': True})
            return data['data']
        except (jwt.InvalidTokenError, KeyError):
            raise exceptions.AuthError(data, code=response.status)
Beispiel #13
0
    def authenticate(self, request):
        try:
            payload = jwt.decode(jwe.decrypt(request.body,
                                             settings.JWE_SECRET),
                                 settings.JWT_SECRET,
                                 options={'verify_exp': False},
                                 algorithm='HS256')
        except (jwt.InvalidTokenError, TypeError):
            raise AuthenticationFailed

        # The JWT `data` payload is expected in the following structure.
        #
        # {"provider": {
        #     "idp": "https://login.circle.edu/idp/shibboleth",
        #     "id": "CIR",
        #     "user": {
        #         "middleNames": "",
        #         "familyName": "",
        #         "givenName": "",
        #         "fullname": "Circle User",
        #         "suffix": "",
        #         "username": "******"
        #     }
        # }}
        data = json.loads(payload['data'])
        provider = data['provider']

        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed(
                'Invalid institution id specified "{}"'.format(provider['id']))

        username = provider['user']['username']
        fullname = provider['user']['fullname']

        user, created = get_or_create_user(fullname,
                                           username,
                                           reset_password=False)

        if created:
            user.given_name = provider['user'].get('givenName')
            user.middle_names = provider['user'].get('middleNames')
            user.family_name = provider['user'].get('familyName')
            user.suffix = provider['user'].get('suffix')
            user.date_last_login = datetime.utcnow()
            user.save()

            # User must be saved in order to have a valid _id
            user.register(username)
            send_mail(to_addr=user.username,
                      mail=WELCOME_OSF4I,
                      mimetype='html',
                      user=user)

        if institution not in user.affiliated_institutions:
            user.affiliated_institutions.append(institution)
            user.save()

        return user, None
Beispiel #14
0
    def authenticate(self, request):
        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256'
            )
        except (jwt.InvalidTokenError, TypeError):
            raise AuthenticationFailed

        # The JWT `data` payload is expected in the following structure.
        #
        # {"provider": {
        #     "idp": "https://login.circle.edu/idp/shibboleth",
        #     "id": "CIR",
        #     "user": {
        #         "middleNames": "",
        #         "familyName": "",
        #         "givenName": "",
        #         "fullname": "Circle User",
        #         "suffix": "",
        #         "username": "******"
        #     }
        # }}
        data = json.loads(payload['data'])
        provider = data['provider']

        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed('Invalid institution id specified "{}"'.format(provider['id']))

        username = provider['user']['username']
        fullname = provider['user']['fullname']

        user, created = get_or_create_user(fullname, username, reset_password=False)

        if created:
            user.given_name = provider['user'].get('givenName')
            user.middle_names = provider['user'].get('middleNames')
            user.family_name = provider['user'].get('familyName')
            user.suffix = provider['user'].get('suffix')
            user.date_last_login = datetime.utcnow()
            user.save()

            # User must be saved in order to have a valid _id
            user.register(username)
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                mimetype='html',
                user=user
            )

        if institution not in user.affiliated_institutions:
            user.affiliated_institutions.append(institution)
            user.save()

        return user, None
Beispiel #15
0
 def to_python(self, value):
     if value and value.startswith(self.prefix):
         value = ensure_bytes(value)
         if not settings.RUNNING_MIGRATION:
             # don't decrypt things if we're migrating.
             value = jwe.decrypt(bytes(value[len(self.prefix):]),
                                 SENSITIVE_DATA_KEY)
         else:
             return value[6:]
     return value
Beispiel #16
0
 def test_auth_download(self):
     url = self.build_url()
     res = self.app.get(url, auth=self.user.auth)
     data = jwt.decode(jwe.decrypt(res.json['payload'].encode('utf-8'), self.JWE_KEY), settings.WATERBUTLER_JWT_SECRET, algorithm=settings.WATERBUTLER_JWT_ALGORITHM)['data']
     assert_equal(data['auth'], views.make_auth(self.user))
     assert_equal(data['credentials'], self.node_addon.serialize_waterbutler_credentials())
     assert_equal(data['settings'], self.node_addon.serialize_waterbutler_settings())
     expected_url = furl.furl(self.node.api_url_for('create_waterbutler_log', _absolute=True, _internal=True))
     observed_url = furl.furl(data['callback_url'])
     observed_url.port = expected_url.port
     assert_equal(expected_url, observed_url)
Beispiel #17
0
 def test_auth_download(self):
     url = self.build_url()
     res = self.app.get(url, auth=self.user.auth)
     data = jwt.decode(jwe.decrypt(res.json['payload'].encode('utf-8'), self.JWE_KEY), settings.WATERBUTLER_JWT_SECRET, algorithm=settings.WATERBUTLER_JWT_ALGORITHM)['data']
     assert_equal(data['auth'], views.make_auth(self.user))
     assert_equal(data['credentials'], self.node_addon.serialize_waterbutler_credentials())
     assert_equal(data['settings'], self.node_addon.serialize_waterbutler_settings())
     expected_url = furl.furl(self.node.api_url_for('create_waterbutler_log', _absolute=True, _internal=True))
     observed_url = furl.furl(data['callback_url'])
     observed_url.port = expected_url.port
     assert_equal(expected_url, observed_url)
Beispiel #18
0
 def to_python(self, value):
     if value and value.startswith(self.prefix):
         value = ensure_bytes(value)
         try:
             value = jwe.decrypt(bytes(value[len(self.prefix):]), SENSITIVE_DATA_KEY)
         except InvalidTag:
             # Allow use of an encrypted DB locally without decrypting fields
             if settings.DEBUG_MODE:
                 pass
             else:
                 raise
     return value
Beispiel #19
0
 def test_database_is_encrypted(self):
     eaf = ExternalAccountFactory(**self.encrypted_field_dict)
     ea = ExternalAccount.objects.get(id=eaf.id)
     ea.reload()
     sql = """
         SELECT %s FROM osf_externalaccount WHERE id = %s;
     """
     with connection.cursor() as cursor:
         cursor.execute(sql, [AsIs(', '.join(self.encrypted_field_dict.keys())), ea.id])
         row = cursor.fetchone()
         for blicky in row:
             assert jwe.decrypt(bytes(blicky[len(EncryptedTextField.prefix):]), SENSITIVE_DATA_KEY) == self.magic_string
def decrypt_string(value, prefix='jwe:::'):
    prefix = ensure_bytes(prefix)
    if value:
        value = ensure_bytes(value)
        if value.startswith(prefix):
            try:
                value = jwe.decrypt(value[len(prefix):], SENSITIVE_DATA_KEY).decode()
            except InvalidTag:
                # Allow use of an encrypted DB locally without decrypting fields
                if settings.DEBUG_MODE:
                    pass
                else:
                    raise
    return value
Beispiel #21
0
 def test_auth_download(self):
     url = self.build_url()
     res = self.app.get(url, auth=self.user.auth)
     data = jwt.decode(
         jwe.decrypt(res.json["payload"].encode("utf-8"), self.JWE_KEY),
         settings.WATERBUTLER_JWT_SECRET,
         algorithm=settings.WATERBUTLER_JWT_ALGORITHM,
     )["data"]
     assert_equal(data["auth"], views.make_auth(self.user))
     assert_equal(data["credentials"], self.node_addon.serialize_waterbutler_credentials())
     assert_equal(data["settings"], self.node_addon.serialize_waterbutler_settings())
     expected_url = furl.furl(self.node.api_url_for("create_waterbutler_log", _absolute=True))
     observed_url = furl.furl(data["callback_url"])
     observed_url.port = expected_url.port
     assert_equal(expected_url, observed_url)
Beispiel #22
0
    def decorated(*args, **kwargs):
        auth = request.headers.get('Authorization', None)
        if not auth:
            return handle_error({'message': 'authorization_header_missing',
                                'description':
                                    'Authorization header is expected'}, 401)
        parts = auth.split()

        if parts[0].lower() != 'bearer':
            return handle_error({'message': 'invalid_header',
                                'description':
                                    'Authorization header must start with'
                                    'Bearer'}, 401)
        elif len(parts) == 1:
            return handle_error({'message': 'invalid_header',
                                'description': 'Token not found'}, 401)
        elif len(parts) > 2:
            return handle_error({'message': 'invalid_header',
                                'description': 'Authorization header must be'
                                 'Bearer + \s + token'}, 401)

        token = parts[1]
        try:
            payload = jwe.decrypt(
                token.encode("utf-8"),
                getEncode()
            )
            dPayLoad = json.loads(payload.decode("utf-8"))
            if not datetime.strptime(dPayLoad['expires'], "%Y-%m-%d %H:%M:%S.%f") > datetime.now():
                raise Exception("token expired")
            if 'accountname' not in dPayLoad:
                raise Exception("Invalid Token")
            # if 'userlevel' not in dPayLoad or dPayLoad['userlevel'] >= 5:
            #     raise Exception("Status Pending")
        except Exception as e:
            return handle_error({'message': str(e),
                                'description': 'Unable to parse authentication : '+str(e)+ ''
                                 ' token.'}, 400)

        _app_ctx_stack.top.current_user = dPayLoad
        return f(*args, **kwargs)
Beispiel #23
0
    def decrypt(self, cookie):

        # Decoding is the reverse of what Accounts does to encode a cookie:
        # Accounts first signs the payload w/ the signature private key, then
        # it next symmetric encrypts that result w/ the encryption private key.

        try:
            decrypted_payload = jwe.decrypt(
                cookie.encode(), self.encryption_private_key.encode())

            decoded_payload = jwt.decode(decrypted_payload,
                                         self.signature_public_key,
                                         audience="OpenStax",
                                         algorithms=[self.signature_algorithm])

            return Payload(decoded_payload)
        except Exception:
            if self.logging_enabled:
                import logging
                logging.exception("Could not decrypt cookie")

            return None
    def authenticate(self, request):
        """
        Handle CAS institution authentication request.

        The JWT `data` payload is expected in the following structure:
        {
            "provider": {
                "idp":  "",
                "id":   "",
                "user": {
                    "username":     "",
                    "fullname":     "",
                    "familyName":   "",
                    "givenName":    "",
                    "middleNames":  "",
                    "suffix":       "",
                }
            }
        }

        :param request: the POST request
        :return: user, None if authentication succeed
        :raises: AuthenticationFailed if authentication fails
        """

        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256',
            )
        except (jwt.InvalidTokenError, TypeError):
            raise AuthenticationFailed

        data = json.loads(payload['data'])
        provider = data['provider']

        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed('Invalid institution id specified "{}"'.format(provider['id']))

        username = provider['user'].get('username')
        fullname = provider['user'].get('fullname')
        given_name = provider['user'].get('givenName')
        family_name = provider['user'].get('familyName')
        middle_names = provider['user'].get('middleNames')
        suffix = provider['user'].get('suffix')

        # use given name and family name to build full name if not provided
        if given_name and family_name and not fullname:
            fullname = given_name + ' ' + family_name

        # institution must provide `fullname`, otherwise we fail the authentication and inform sentry
        if not fullname:
            message = 'Institution login failed: fullname required' \
                      ' for user {} from institution {}'.format(username, provider['id'])
            sentry.log_message(message)
            raise AuthenticationFailed(message)

        # `get_or_create_user()` guesses names from fullname
        # replace the guessed ones if the names are provided from the authentication
        user, created = get_or_create_user(fullname, username, reset_password=False)
        if created:
            if given_name:
                user.given_name = given_name
            if family_name:
                user.family_name = family_name
            if middle_names:
                user.middle_names = middle_names
            if suffix:
                user.suffix = suffix
            user.update_date_last_login()

            # Relying on front-end validation until `accepted_tos` is added to the JWT payload
            user.accepted_terms_of_service = timezone.now()

            # save and register user
            user.save()
            user.register(username)

            # send confirmation email
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                mimetype='html',
                user=user,
                domain=DOMAIN,
                osf_support_email=OSF_SUPPORT_EMAIL,
                storage_flag_is_active=waffle.flag_is_active(request, features.STORAGE_I18N),
            )

        if not user.is_affiliated_with_institution(institution):
            user.affiliated_institutions.add(institution)
            user.save()

        return user, None
Beispiel #25
0
def get_auth(auth, **kwargs):
    cas_resp = None
    if not auth.user:
        # Central Authentication Server OAuth Bearer Token
        authorization = request.headers.get('Authorization')
        if authorization and authorization.startswith('Bearer '):
            client = cas.get_client()
            try:
                access_token = cas.parse_auth_header(authorization)
                cas_resp = client.profile(access_token)
            except cas.CasError as err:
                sentry.log_exception()
                # NOTE: We assume that the request is an AJAX request
                return json_renderer(err)
            if cas_resp.authenticated:
                auth.user = OSFUser.load(cas_resp.user)

    try:
        data = jwt.decode(jwe.decrypt(
            request.args.get('payload', '').encode('utf-8'),
            WATERBUTLER_JWE_KEY),
                          settings.WATERBUTLER_JWT_SECRET,
                          options={'require_exp': True},
                          algorithm=settings.WATERBUTLER_JWT_ALGORITHM)['data']
    except (jwt.InvalidTokenError, KeyError) as err:
        sentry.log_message(str(err))
        raise HTTPError(http_status.HTTP_403_FORBIDDEN)

    if not auth.user:
        auth.user = OSFUser.from_cookie(data.get('cookie', ''))

    try:
        action = data['action']
        node_id = data['nid']
        provider_name = data['provider']
    except KeyError:
        raise HTTPError(http_status.HTTP_400_BAD_REQUEST)

    node = AbstractNode.load(node_id) or Preprint.load(node_id)
    if node and node.is_deleted:
        raise HTTPError(http_status.HTTP_410_GONE)
    elif not node:
        raise HTTPError(http_status.HTTP_404_NOT_FOUND)

    check_access(node, auth, action, cas_resp)
    provider_settings = None
    if hasattr(node, 'get_addon'):
        provider_settings = node.get_addon(provider_name)
        if not provider_settings:
            raise HTTPError(http_status.HTTP_400_BAD_REQUEST)

    path = data.get('path')
    credentials = None
    waterbutler_settings = None
    fileversion = None
    if provider_name == 'osfstorage':
        if path:
            file_id = path.strip('/')
            # check to see if this is a file or a folder
            filenode = OsfStorageFileNode.load(path.strip('/'))
            if filenode and filenode.is_file:
                # default to most recent version if none is provided in the response
                version = int(data['version']) if data.get(
                    'version') else filenode.versions.count()
                try:
                    fileversion = FileVersion.objects.filter(
                        basefilenode___id=file_id,
                        identifier=version).select_related('region').get()
                except FileVersion.DoesNotExist:
                    raise HTTPError(http_status.HTTP_400_BAD_REQUEST)
                if auth.user:
                    # mark fileversion as seen
                    FileVersionUserMetadata.objects.get_or_create(
                        user=auth.user, file_version=fileversion)
                if not node.is_contributor_or_group_member(auth.user):
                    from_mfr = download_is_from_mfr(request, payload=data)
                    # version index is 0 based
                    version_index = version - 1
                    if action == 'render':
                        update_analytics(node, filenode, version_index, 'view')
                    elif action == 'download' and not from_mfr:
                        update_analytics(node, filenode, version_index,
                                         'download')
                    if waffle.switch_is_active(features.ELASTICSEARCH_METRICS):
                        if isinstance(node, Preprint):
                            metric_class = get_metric_class_for_action(
                                action, from_mfr=from_mfr)
                            if metric_class:
                                sloan_flags = {
                                    'sloan_id':
                                    request.cookies.get(SLOAN_ID_COOKIE_NAME)
                                }
                                for flag_name in SLOAN_FLAGS:
                                    value = request.cookies.get(
                                        f'dwf_{flag_name}_custom_domain'
                                    ) or request.cookies.get(
                                        f'dwf_{flag_name}')
                                    if value:
                                        sloan_flags[flag_name.replace(
                                            '_display', '')] = strtobool(value)

                                try:
                                    metric_class.record_for_preprint(
                                        preprint=node,
                                        user=auth.user,
                                        version=fileversion.identifier
                                        if fileversion else None,
                                        path=path,
                                        **sloan_flags)
                                except es_exceptions.ConnectionError:
                                    log_exception()
        if fileversion and provider_settings:
            region = fileversion.region
            credentials = region.waterbutler_credentials
            waterbutler_settings = fileversion.serialize_waterbutler_settings(
                node_id=provider_settings.owner._id,
                root_id=provider_settings.root_node._id,
            )
    # If they haven't been set by version region, use the NodeSettings or Preprint directly
    if not (credentials and waterbutler_settings):
        credentials = node.serialize_waterbutler_credentials(provider_name)
        waterbutler_settings = node.serialize_waterbutler_settings(
            provider_name)

    if isinstance(credentials.get('token'), bytes):
        credentials['token'] = credentials.get('token').decode()

    return {
        'payload':
        jwe.encrypt(
            jwt.encode(
                {
                    'exp':
                    timezone.now() + datetime.timedelta(
                        seconds=settings.WATERBUTLER_JWT_EXPIRATION),
                    'data': {
                        'auth':
                        make_auth(
                            auth.user
                        ),  # A waterbutler auth dict not an Auth object
                        'credentials':
                        credentials,
                        'settings':
                        waterbutler_settings,
                        'callback_url':
                        node.api_url_for(
                            ('create_waterbutler_log'
                             if not getattr(node, 'is_registration', False)
                             else 'registration_callbacks'),
                            _absolute=True,
                            _internal=True)
                    }
                },
                settings.WATERBUTLER_JWT_SECRET,
                algorithm=settings.WATERBUTLER_JWT_ALGORITHM),
            WATERBUTLER_JWE_KEY).decode()
    }
Beispiel #26
0
def decrypt(value):
    if value:
        return jwe.decrypt(value.encode('utf-8'), SENSITIVE_DATA_KEY)
    return None
Beispiel #27
0
def encrypt_key(document, key):
    database['externalaccount'].find_and_modify(
        {'_id': document['_id']},
        {'$set': {
            key: jwe.encrypt(bytes(jwe.decrypt(document[key].encode('utf-8'), SENSITIVE_DATA_KEY)), SENSITIVE_DATA_KEY)
        }})
Beispiel #28
0
def get_auth(auth, **kwargs):
    cas_resp = None
    if not auth.user:
        # Central Authentication Server OAuth Bearer Token
        authorization = request.headers.get('Authorization')
        if authorization and authorization.startswith('Bearer '):
            client = cas.get_client()
            try:
                access_token = cas.parse_auth_header(authorization)
                cas_resp = client.profile(access_token)
            except cas.CasError as err:
                sentry.log_exception()
                # NOTE: We assume that the request is an AJAX request
                return json_renderer(err)
            if cas_resp.authenticated:
                auth.user = User.load(cas_resp.user)

    try:
        data = jwt.decode(
            jwe.decrypt(request.args.get('payload', '').encode('utf-8'), WATERBUTLER_JWE_KEY),
            settings.WATERBUTLER_JWT_SECRET,
            options={'require_exp': True},
            algorithm=settings.WATERBUTLER_JWT_ALGORITHM
        )['data']
    except (jwt.InvalidTokenError, KeyError):
        raise HTTPError(httplib.FORBIDDEN)

    if not auth.user:
        auth.user = User.from_cookie(data.get('cookie', ''))

    try:
        action = data['action']
        node_id = data['nid']
        provider_name = data['provider']
    except KeyError:
        raise HTTPError(httplib.BAD_REQUEST)

    node = Node.load(node_id)
    if not node:
        raise HTTPError(httplib.NOT_FOUND)

    check_access(node, auth, action, cas_resp)

    provider_settings = node.get_addon(provider_name)
    if not provider_settings:
        raise HTTPError(httplib.BAD_REQUEST)

    try:
        credentials = provider_settings.serialize_waterbutler_credentials()
        waterbutler_settings = provider_settings.serialize_waterbutler_settings()
    except exceptions.AddonError:
        log_exception()
        raise HTTPError(httplib.BAD_REQUEST)

    return {'payload': jwe.encrypt(jwt.encode({
        'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.WATERBUTLER_JWT_EXPIRATION),
        'data': {
            'auth': make_auth(auth.user),  # A waterbutler auth dict not an Auth object
            'credentials': credentials,
            'settings': waterbutler_settings,
            'callback_url': node.api_url_for(
                ('create_waterbutler_log' if not node.is_registration else 'registration_callbacks'),
                _absolute=True,
            ),
        }
    }, settings.WATERBUTLER_JWT_SECRET, algorithm=settings.WATERBUTLER_JWT_ALGORITHM), WATERBUTLER_JWE_KEY)}
Beispiel #29
0
    def authenticate(self, request):
        """
        Handle CAS institution authentication request.

        The JWT `data` payload is expected in the following structure:
        {
            "provider": {
                "idp":  "",
                "id":   "",
                "user": {
                    "username":     "",
                    "fullname":     "",
                    "familyName":   "",
                    "givenName":    "",
                    "middleNames":  "",
                    "suffix":       "",
                }
            }
        }

        :param request: the POST request
        :return: user, None if authentication succeed
        :raises: AuthenticationFailed if authentication fails
        """

        # Verify / decrypt / decode the payload
        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256',
            )
        except (jwt.InvalidTokenError, TypeError, jwe.exceptions.MalformedData):
            raise AuthenticationFailed

        # Load institution and user data
        data = json.loads(payload['data'])
        provider = data['provider']
        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed('Invalid institution id: "{}"'.format(provider['id']))
        username = provider['user'].get('username')
        fullname = provider['user'].get('fullname')
        given_name = provider['user'].get('givenName')
        family_name = provider['user'].get('familyName')
        middle_names = provider['user'].get('middleNames')
        suffix = provider['user'].get('suffix')
        department = provider['user'].get('department')

        # Use given name and family name to build full name if it is not provided
        if given_name and family_name and not fullname:
            fullname = given_name + ' ' + family_name

        # Non-empty full name is required. Fail the auth and inform sentry if not provided.
        if not fullname:
            message = 'Institution login failed: fullname required for ' \
                      'user "{}" from institution "{}"'.format(username, provider['id'])
            sentry.log_message(message)
            raise AuthenticationFailed(message)

        # Get an existing user or create a new one. If a new user is created, the user object is
        # confirmed but not registered,which is temporarily of an inactive status. If an existing
        # user is found, it is also possible that the user is inactive (e.g. unclaimed, disabled,
        # unconfirmed, etc.).
        user, created = get_or_create_user(fullname, username, reset_password=False)

        # Existing but inactive users need to be either "activated" or failed the auth
        activation_required = False
        new_password_required = False
        if not created:
            try:
                drf.check_user(user)
                logger.info('Institution SSO: active user "{}"'.format(username))
            except exceptions.UnclaimedAccountError:
                # Unclaimed user (i.e. a user that has been added as an unregistered contributor)
                user.unclaimed_records = {}
                activation_required = True
                # Unclaimed users have an unusable password when being added as an unregistered
                # contributor. Thus a random usable password must be assigned during activation.
                new_password_required = True
                logger.info('Institution SSO: unclaimed contributor "{}"'.format(username))
            except exceptions.UnconfirmedAccountError:
                if user.has_usable_password():
                    # Unconfirmed user from default username / password signup
                    user.email_verifications = {}
                    activation_required = True
                    # Unconfirmed users already have a usable password set by the creator during
                    # sign-up. However, it must be overwritten by a new random one so the creator
                    # (if he is not the real person) can not access the account after activation.
                    new_password_required = True
                    logger.info('Institution SSO: unconfirmed user "{}"'.format(username))
                else:
                    # Login take-over has not been implemented for unconfirmed user created via
                    # external IdP login (ORCiD).
                    message = 'Institution SSO is not eligible for an unconfirmed account ' \
                              'created via external IdP login: username = "******"'.format(username)
                    sentry.log_message(message)
                    logger.error(message)
                    return None, None
            except exceptions.DeactivatedAccountError:
                # Deactivated user: login is not allowed for deactivated users
                message = 'Institution SSO is not eligible for a deactivated account: ' \
                          'username = "******"'.format(username)
                sentry.log_message(message)
                logger.error(message)
                return None, None
            except exceptions.MergedAccountError:
                # Merged user: this shouldn't happen since merged users do not have an email
                message = 'Institution SSO is not eligible for a merged account: ' \
                          'username = "******"'.format(username)
                sentry.log_message(message)
                logger.error(message)
                return None, None
            except exceptions.InvalidAccountError:
                # Other invalid status: this shouldn't happen unless the user happens to be in a
                # temporary state. Such state requires more updates before the user can be saved
                # to the database. (e.g. `get_or_create_user()` creates a temporary-state user.)
                message = 'Institution SSO is not eligible for an inactive account with ' \
                          'an unknown or invalid status: username = "******"'.format(username)
                sentry.log_message(message)
                logger.error(message)
                return None, None
        else:
            logger.info('Institution SSO: new user "{}"'.format(username))

        # The `department` field is updated each login when it was changed.
        if department and user.department != department:
            user.department = department
            user.save()

        # Both created and activated accounts need to be updated and registered
        if created or activation_required:

            if given_name:
                user.given_name = given_name
            if family_name:
                user.family_name = family_name
            if middle_names:
                user.middle_names = middle_names
            if suffix:
                user.suffix = suffix

            # Users claimed or confirmed via institution SSO should have their full name updated
            if activation_required:
                user.fullname = fullname

            user.update_date_last_login()

            # Relying on front-end validation until `accepted_tos` is added to the JWT payload
            user.accepted_terms_of_service = timezone.now()

            # Register and save user
            password = str(uuid.uuid4()) if new_password_required else None
            user.register(username, password=password)
            user.save()

            # Send confirmation email for all three: created, confirmed and claimed
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                mimetype='html',
                user=user,
                domain=DOMAIN,
                osf_support_email=OSF_SUPPORT_EMAIL,
                storage_flag_is_active=waffle.flag_is_active(request, features.STORAGE_I18N),
            )

        # Affiliate the user if not previously affiliated
        if not user.is_affiliated_with_institution(institution):
            user.affiliated_institutions.add(institution)
            user.save()

        return user, None
Beispiel #30
0
    def authenticate(self, request):
        """
        Handle CAS institution authentication request.

        The JWT `data` payload is expected in the following structure:
        {
            "provider": {
                "idp": "",
                "id": "",
                "user": {
                    "username": "",
                    "fullname": "",
                    "familyName": "",
                    "givenName": "",
                    "middleNames": "",
                    "suffix": "",
                    "department": "",
                    "isMemberOf": "",  # Shared SSO
                    "selectiveSsoFilter": "",  # Selective SSO
                }
            }
        }

        Note that if authentication failed, HTTP 403 Forbidden is returned no matter what type of
        exception is raised. In this method, we use `AuthenticationFailed` when the payload is not
        correctly encrypted/encoded since it is the "authentication" between CAS and this endpoint.
        We use `PermissionDenied` for all other exceptions that happened afterwards.

        :param request: the POST request
        :return: user, None if authentication succeed
        :raises: AuthenticationFailed or PermissionDenied if authentication fails
        """

        # Verify / decrypt / decode the payload
        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256',
            )
        except (jwt.InvalidTokenError, TypeError,
                jwe.exceptions.MalformedData):
            raise AuthenticationFailed(
                detail='InstitutionSsoRequestNotAuthorized')

        # Load institution and user data
        data = json.loads(payload['data'])
        provider = data['provider']
        institution = Institution.load(provider['id'])
        if not institution:
            message = 'Institution SSO Error: invalid institution ID [{}]'.format(
                provider['id'])
            logger.error(message)
            sentry.log_message(message)
            raise PermissionDenied(detail='InstitutionSsoInvalidInstitution')
        username = provider['user'].get('username')
        fullname = provider['user'].get('fullname')
        given_name = provider['user'].get('givenName')
        family_name = provider['user'].get('familyName')
        middle_names = provider['user'].get('middleNames')
        suffix = provider['user'].get('suffix')
        department = provider['user'].get('department')
        selective_sso_filter = provider['user'].get('selectiveSsoFilter')

        # Check selective login first
        if provider['id'] in INSTITUTION_SELECTIVE_SSO_MAP:
            if selective_sso_filter != INSTITUTION_SELECTIVE_SSO_MAP[
                    provider['id']]:
                message = f'Institution SSO Error: user [email={username}] is not allowed for ' \
                          f'institution SSO [id={institution._id}] due to selective SSO rules'
                logger.error(message)
                sentry.log_message(message)
                raise PermissionDenied(
                    detail='InstitutionSsoSelectiveNotAllowed')
            logger.info(
                f'Institution SSO: selective SSO verified for user [email={username}] '
                f'at institution [id={institution._id}]', )

        # Check secondary institutions which uses the SSO of primary ones
        secondary_institution = None
        if provider['id'] in INSTITUTION_SHARED_SSO_MAP:
            switch_map = INSTITUTION_SHARED_SSO_MAP[provider['id']]
            criteria_type = switch_map.get('criteria')
            if criteria_type == 'attribute':
                attribute_name = switch_map.get('attribute')
                attribute_value = provider['user'].get(attribute_name)
                if attribute_value:
                    secondary_institution_id = switch_map.get(
                        'institutions',
                        {},
                    ).get(attribute_value)
                    logger.info(
                        'Institution SSO: primary=[{}], secondary=[{}], '
                        'username=[{}]'.format(provider['id'],
                                               secondary_institution_id,
                                               username))
                    secondary_institution = Institution.load(
                        secondary_institution_id)
                    if not secondary_institution:
                        # Log errors and inform Sentry but do not raise an exception if OSF fails
                        # to load the secondary institution from database
                        message = 'Institution SSO Error: invalid secondary institution [{}]; ' \
                                  'primary=[{}], username=[{}]'.format(attribute_value, provider['id'], username)
                        logger.error(message)
                        sentry.log_message(message)
                else:
                    # SSO from primary institution only
                    logger.info(
                        'Institution SSO: primary=[{}], secondary=[None], '
                        'username=[{}]'.format(provider['id'], username))
            else:
                message = 'Institution SSO Error: invalid criteria [{}]; ' \
                          'primary=[{}], username=[{}]'.format(criteria_type, provider['id'], username)
                logger.error(message)
                sentry.log_message(message)

        # Use given name and family name to build full name if it is not provided
        if given_name and family_name and not fullname:
            fullname = given_name + ' ' + family_name

        # Non-empty full name is required. Fail the auth and inform sentry if not provided.
        if not fullname:
            message = 'Institution SSO Error: missing fullname ' \
                      'for user [{}] from institution [{}]'.format(username, provider['id'])
            logger.error(message)
            sentry.log_message(message)
            raise PermissionDenied(detail='InstitutionSsoMissingUserNames')

        # Get an existing user or create a new one. If a new user is created, the user object is
        # confirmed but not registered,which is temporarily of an inactive status. If an existing
        # user is found, it is also possible that the user is inactive (e.g. unclaimed, disabled,
        # unconfirmed, etc.).
        user, created = get_or_create_user(fullname,
                                           username,
                                           reset_password=False)

        # Existing but inactive users need to be either "activated" or failed the auth
        activation_required = False
        new_password_required = False
        if not created:
            try:
                drf.check_user(user)
                logger.info(
                    'Institution SSO: active user [{}]'.format(username))
            except exceptions.UnclaimedAccountError:
                # Unclaimed user (i.e. a user that has been added as an unregistered contributor)
                user.unclaimed_records = {}
                activation_required = True
                # Unclaimed users have an unusable password when being added as an unregistered
                # contributor. Thus a random usable password must be assigned during activation.
                new_password_required = True
                logger.warning(
                    'Institution SSO: unclaimed contributor [{}]'.format(
                        username))
            except exceptions.UnconfirmedAccountError:
                if user.has_usable_password():
                    # Unconfirmed user from default username / password signup
                    user.email_verifications = {}
                    activation_required = True
                    # Unconfirmed users already have a usable password set by the creator during
                    # sign-up. However, it must be overwritten by a new random one so the creator
                    # (if he is not the real person) can not access the account after activation.
                    new_password_required = True
                    logger.warning(
                        'Institution SSO: unconfirmed user [{}]'.format(
                            username))
                else:
                    # Login take-over has not been implemented for unconfirmed user created via
                    # external IdP login (ORCiD).
                    message = 'Institution SSO Error: SSO is not eligible for an unconfirmed account [{}] ' \
                              'created via IdP login'.format(username)
                    sentry.log_message(message)
                    logger.error(message)
                    raise PermissionDenied(
                        detail='InstitutionSsoAccountNotConfirmed')
            except exceptions.DeactivatedAccountError:
                # Deactivated user: login is not allowed for deactivated users
                message = 'Institution SSO Error: SSO is not eligible for a deactivated account: [{}]'.format(
                    username)
                sentry.log_message(message)
                logger.error(message)
                raise PermissionDenied(detail='InstitutionSsoAccountDisabled')
            except exceptions.MergedAccountError:
                # Merged user: this shouldn't happen since merged users do not have an email
                message = 'Institution SSO Error: SSO is not eligible for a merged account: [{}]'.format(
                    username)
                sentry.log_message(message)
                logger.error(message)
                raise PermissionDenied(detail='InstitutionSsoAccountMerged')
            except exceptions.InvalidAccountError:
                # Other invalid status: this shouldn't happen unless the user happens to be in a
                # temporary state. Such state requires more updates before the user can be saved
                # to the database. (e.g. `get_or_create_user()` creates a temporary-state user.)
                message = 'Institution SSO Error: SSO is not eligible for an inactive account [{}] ' \
                          'with an unknown or invalid status'.format(username)
                sentry.log_message(message)
                logger.error(message)
                raise PermissionDenied(detail='InstitutionSsoInvalidAccount')
        else:
            logger.info('Institution SSO: new user [{}]'.format(username))

        # The `department` field is updated each login when it was changed.
        user_guid = user.guids.first()._id
        if department:
            if user.department != department:
                user.department = department
                user.save()
            logger.info(
                'Institution SSO: user w/ dept: user=[{}], email=[{}], inst=[{}], '
                'dept=[{}]'.format(user_guid, username, institution._id,
                                   department))
        else:
            logger.info(
                'Institution SSO: user w/o dept: user=[{}], email=[{}], '
                'inst=[{}]'.format(user_guid, username, institution._id))

        # Both created and activated accounts need to be updated and registered
        if created or activation_required:

            if given_name:
                user.given_name = given_name
            if family_name:
                user.family_name = family_name
            if middle_names:
                user.middle_names = middle_names
            if suffix:
                user.suffix = suffix

            # Users claimed or confirmed via institution SSO should have their full name updated
            if activation_required:
                user.fullname = fullname

            user.update_date_last_login()

            # Register and save user
            password = str(uuid.uuid4()) if new_password_required else None
            user.register(username, password=password)
            user.save()

            # Send confirmation email for all three: created, confirmed and claimed
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                user=user,
                domain=DOMAIN,
                osf_support_email=OSF_SUPPORT_EMAIL,
                storage_flag_is_active=waffle.flag_is_active(
                    request, features.STORAGE_I18N),
            )

        # Affiliate the user to the primary institution if not previously affiliated
        if not user.is_affiliated_with_institution(institution):
            user.affiliated_institutions.add(institution)
            user.save()

        # Affiliate the user to the secondary institution if not previously affiliated
        if secondary_institution and not user.is_affiliated_with_institution(
                secondary_institution):
            user.affiliated_institutions.add(secondary_institution)
            user.save()

        return user, None
Beispiel #31
0
def decrypt(value):
    if value:
        return jwe.decrypt(bytes(value), SENSITIVE_DATA_KEY)
    return None
Beispiel #32
0
 def test_invalid_data(self):
     with pytest.raises(jwe.exceptions.MalformedData):
         jwe.decrypt(b"junkdata", jwe.kdf(b"key", b"Salt"))
Beispiel #33
0
def get_auth(auth, **kwargs):
    cas_resp = None
    if not auth.user:
        # Central Authentication Server OAuth Bearer Token
        authorization = request.headers.get('Authorization')
        if authorization and authorization.startswith('Bearer '):
            client = cas.get_client()
            try:
                access_token = cas.parse_auth_header(authorization)
                cas_resp = client.profile(access_token)
            except cas.CasError as err:
                sentry.log_exception()
                # NOTE: We assume that the request is an AJAX request
                return json_renderer(err)
            if cas_resp.authenticated:
                auth.user = OSFUser.load(cas_resp.user)

    try:
        data = jwt.decode(
            jwe.decrypt(request.args.get('payload', '').encode('utf-8'), WATERBUTLER_JWE_KEY),
            settings.WATERBUTLER_JWT_SECRET,
            options={'require_exp': True},
            algorithm=settings.WATERBUTLER_JWT_ALGORITHM
        )['data']
    except (jwt.InvalidTokenError, KeyError) as err:
        sentry.log_message(str(err))
        raise HTTPError(httplib.FORBIDDEN)

    if not auth.user:
        auth.user = OSFUser.from_cookie(data.get('cookie', ''))

    try:
        action = data['action']
        node_id = data['nid']
        provider_name = data['provider']
    except KeyError:
        raise HTTPError(httplib.BAD_REQUEST)

    node = AbstractNode.load(node_id) or Preprint.load(node_id)
    if not node:
        raise HTTPError(httplib.NOT_FOUND)

    check_access(node, auth, action, cas_resp)
    provider_settings = None
    if hasattr(node, 'get_addon'):
        provider_settings = node.get_addon(provider_name)
        if not provider_settings:
            raise HTTPError(httplib.BAD_REQUEST)

    try:
        path = data.get('path')
        version = data.get('version')
        credentials = None
        waterbutler_settings = None
        fileversion = None
        if provider_name == 'osfstorage':
            if path and version:
                # check to see if this is a file or a folder
                filenode = OsfStorageFileNode.load(path.strip('/'))
                if filenode and filenode.is_file:
                    try:
                        fileversion = FileVersion.objects.filter(
                            basefilenode___id=path.strip('/'),
                            identifier=version
                        ).select_related('region').get()
                    except FileVersion.DoesNotExist:
                        raise HTTPError(httplib.BAD_REQUEST)
            # path and no version, use most recent version
            elif path:
                filenode = OsfStorageFileNode.load(path.strip('/'))
                if filenode and filenode.is_file:
                    fileversion = FileVersion.objects.filter(
                        basefilenode=filenode
                    ).select_related('region').order_by('-created').first()
            if fileversion:
                region = fileversion.region
                credentials = region.waterbutler_credentials
                waterbutler_settings = fileversion.serialize_waterbutler_settings(
                    node_id=provider_settings.owner._id if provider_settings else node._id,
                    root_id=provider_settings.root_node._id if provider_settings else node.root_folder._id,
                )
        # If they haven't been set by version region, use the NodeSettings region
        if not (credentials and waterbutler_settings):
            credentials = node.serialize_waterbutler_credentials(provider_name)
            waterbutler_settings = node.serialize_waterbutler_settings(provider_name)
    except exceptions.AddonError:
        log_exception()
        raise HTTPError(httplib.BAD_REQUEST)

    # TODO: Add a signal here?
    if waffle.switch_is_active(features.ELASTICSEARCH_METRICS):
        user = auth.user
        if isinstance(node, Preprint) and not node.is_contributor(user):
            metric_class = get_metric_class_for_action(action)
            if metric_class:
                try:
                    metric_class.record_for_preprint(
                        preprint=node,
                        user=user,
                        version=fileversion.identifier if fileversion else None,
                        path=path
                    )
                except es_exceptions.ConnectionError:
                    log_exception()

    return {'payload': jwe.encrypt(jwt.encode({
        'exp': timezone.now() + datetime.timedelta(seconds=settings.WATERBUTLER_JWT_EXPIRATION),
        'data': {
            'auth': make_auth(auth.user),  # A waterbutler auth dict not an Auth object
            'credentials': credentials,
            'settings': waterbutler_settings,
            'callback_url': node.api_url_for(
                ('create_waterbutler_log' if not getattr(node, 'is_registration', False) else 'registration_callbacks'),
                _absolute=True,
                _internal=True
            )
        }
    }, settings.WATERBUTLER_JWT_SECRET, algorithm=settings.WATERBUTLER_JWT_ALGORITHM), WATERBUTLER_JWE_KEY)}
Beispiel #34
0
    def authenticate(self, request):
        """
        Handle CAS institution authentication request.

        The JWT `data` payload is expected in the following structure:
        {
            "provider": {
                "idp":  "",
                "id":   "",
                "user": {
                    "username":     "",
                    "fullname":     "",
                    "familyName":   "",
                    "givenName":    "",
                    "middleNames":  "",
                    "suffix":       "",
                }
            }
        }

        :param request: the POST request
        :return: user, None if authentication succeed
        :raises: AuthenticationFailed if authentication fails
        """

        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256',
            )
        except (jwt.InvalidTokenError, TypeError,
                jwe.exceptions.MalformedData):
            raise AuthenticationFailed

        data = json.loads(payload['data'])
        provider = data['provider']

        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed(
                'Invalid institution id specified "{}"'.format(provider['id']))

        username = provider['user'].get('username')
        fullname = provider['user'].get('fullname')
        given_name = provider['user'].get('givenName')
        family_name = provider['user'].get('familyName')
        middle_names = provider['user'].get('middleNames')
        suffix = provider['user'].get('suffix')

        # use given name and family name to build full name if not provided
        if given_name and family_name and not fullname:
            fullname = given_name + ' ' + family_name

        # institution must provide `fullname`, otherwise we fail the authentication and inform sentry
        if not fullname:
            message = 'Institution login failed: fullname required' \
                      ' for user {} from institution {}'.format(username, provider['id'])
            sentry.log_message(message)
            raise AuthenticationFailed(message)

        # `get_or_create_user()` guesses names from fullname
        # replace the guessed ones if the names are provided from the authentication
        user, created = get_or_create_user(fullname,
                                           username,
                                           reset_password=False)
        if created:
            if given_name:
                user.given_name = given_name
            if family_name:
                user.family_name = family_name
            if middle_names:
                user.middle_names = middle_names
            if suffix:
                user.suffix = suffix
            user.update_date_last_login()

            # Relying on front-end validation until `accepted_tos` is added to the JWT payload
            user.accepted_terms_of_service = timezone.now()

            # save and register user
            user.save()
            user.register(username)

            # send confirmation email
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                mimetype='html',
                user=user,
                domain=DOMAIN,
                osf_support_email=OSF_SUPPORT_EMAIL,
                storage_flag_is_active=waffle.flag_is_active(
                    request, features.STORAGE_I18N),
            )

        if not user.is_affiliated_with_institution(institution):
            user.affiliated_institutions.add(institution)
            user.save()

        return user, None
Beispiel #35
0
def decrypt(value):
    if value:
        value = ensure_bytes(value)
        return jwe.decrypt(bytes(value), SENSITIVE_DATA_KEY)
    return None
Beispiel #36
0
def get_auth(auth, **kwargs):
    cas_resp = None
    if not auth.user:
        # Central Authentication Server OAuth Bearer Token
        authorization = request.headers.get('Authorization')
        if authorization and authorization.startswith('Bearer '):
            client = cas.get_client()
            try:
                access_token = cas.parse_auth_header(authorization)
                cas_resp = client.profile(access_token)
            except cas.CasError as err:
                sentry.log_exception()
                # NOTE: We assume that the request is an AJAX request
                return json_renderer(err)
            if cas_resp.authenticated:
                auth.user = OSFUser.load(cas_resp.user)

    try:
        data = jwt.decode(jwe.decrypt(
            request.args.get('payload', '').encode('utf-8'),
            WATERBUTLER_JWE_KEY),
                          settings.WATERBUTLER_JWT_SECRET,
                          options={'require_exp': True},
                          algorithm=settings.WATERBUTLER_JWT_ALGORITHM)['data']
    except (jwt.InvalidTokenError, KeyError) as err:
        sentry.log_message(str(err))
        raise HTTPError(httplib.FORBIDDEN)

    if not auth.user:
        auth.user = OSFUser.from_cookie(data.get('cookie', ''))

    try:
        action = data['action']
        node_id = data['nid']
        provider_name = data['provider']
    except KeyError:
        raise HTTPError(httplib.BAD_REQUEST)

    node = AbstractNode.load(node_id)
    if not node:
        raise HTTPError(httplib.NOT_FOUND)

    check_access(node, auth, action, cas_resp)

    provider_settings = node.get_addon(provider_name)
    if not provider_settings:
        raise HTTPError(httplib.BAD_REQUEST)

    try:
        credentials = provider_settings.serialize_waterbutler_credentials()
        waterbutler_settings = provider_settings.serialize_waterbutler_settings(
        )
    except exceptions.AddonError:
        log_exception()
        raise HTTPError(httplib.BAD_REQUEST)

    return {
        'payload':
        jwe.encrypt(
            jwt.encode(
                {
                    'exp':
                    timezone.now() + datetime.timedelta(
                        seconds=settings.WATERBUTLER_JWT_EXPIRATION),
                    'data': {
                        'auth':
                        make_auth(
                            auth.user
                        ),  # A waterbutler auth dict not an Auth object
                        'credentials':
                        credentials,
                        'settings':
                        waterbutler_settings,
                        'callback_url':
                        node.api_url_for(('create_waterbutler_log'
                                          if not node.is_registration else
                                          'registration_callbacks'),
                                         _absolute=True,
                                         _internal=True),
                    }
                },
                settings.WATERBUTLER_JWT_SECRET,
                algorithm=settings.WATERBUTLER_JWT_ALGORITHM),
            WATERBUTLER_JWE_KEY)
    }
Beispiel #37
0
 def test_invalid_data(self):
     with pytest.raises(jwe.exceptions.MalformedData):
         jwe.decrypt(b'junkdata', jwe.kdf(b'key', b'Salt'))
Beispiel #38
0
def get_auth(auth, **kwargs):
    cas_resp = None
    if not auth.user:
        # Central Authentication Server OAuth Bearer Token
        authorization = request.headers.get("Authorization")
        if authorization and authorization.startswith("Bearer "):
            client = cas.get_client()
            try:
                access_token = cas.parse_auth_header(authorization)
                cas_resp = client.profile(access_token)
            except cas.CasError as err:
                sentry.log_exception()
                # NOTE: We assume that the request is an AJAX request
                return json_renderer(err)
            if cas_resp.authenticated:
                auth.user = User.load(cas_resp.user)

    try:
        data = jwt.decode(
            jwe.decrypt(request.args.get("payload", "").encode("utf-8"), WATERBUTLER_JWE_KEY),
            settings.WATERBUTLER_JWT_SECRET,
            options={"require_exp": True},
            algorithm=settings.WATERBUTLER_JWT_ALGORITHM,
        )["data"]
    except (jwt.InvalidTokenError, KeyError):
        raise HTTPError(httplib.FORBIDDEN)

    if not auth.user:
        auth.user = User.from_cookie(data.get("cookie", ""))

    try:
        action = data["action"]
        node_id = data["nid"]
        provider_name = data["provider"]
    except KeyError:
        raise HTTPError(httplib.BAD_REQUEST)

    node = Node.load(node_id)
    if not node:
        raise HTTPError(httplib.NOT_FOUND)

    check_access(node, auth, action, cas_resp)

    provider_settings = node.get_addon(provider_name)
    if not provider_settings:
        raise HTTPError(httplib.BAD_REQUEST)

    try:
        credentials = provider_settings.serialize_waterbutler_credentials()
        waterbutler_settings = provider_settings.serialize_waterbutler_settings()
    except exceptions.AddonError:
        log_exception()
        raise HTTPError(httplib.BAD_REQUEST)

    return {
        "payload": jwe.encrypt(
            jwt.encode(
                {
                    "exp": datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.WATERBUTLER_JWT_EXPIRATION),
                    "data": {
                        "auth": make_auth(auth.user),  # A waterbutler auth dict not an Auth object
                        "credentials": credentials,
                        "settings": waterbutler_settings,
                        "callback_url": node.api_url_for(
                            ("create_waterbutler_log" if not node.is_registration else "registration_callbacks"),
                            _absolute=True,
                        ),
                    },
                },
                settings.WATERBUTLER_JWT_SECRET,
                algorithm=settings.WATERBUTLER_JWT_ALGORITHM,
            ),
            WATERBUTLER_JWE_KEY,
        )
    }