コード例 #1
0
ファイル: views.py プロジェクト: johngian/mozillians
 def __init__(self, *args, **kwargs):
     """Initialize settings."""
     self.OIDC_OP_TOKEN_ENDPOINT = import_from_settings('OIDC_OP_TOKEN_ENDPOINT')
     self.OIDC_OP_USER_ENDPOINT = import_from_settings('OIDC_OP_USER_ENDPOINT')
     self.OIDC_RP_VERIFICATION_CLIENT_ID = (
         import_from_settings('OIDC_RP_VERIFICATION_CLIENT_ID')
     )
     self.OIDC_RP_VERIFICATION_CLIENT_SECRET = (
         import_from_settings('OIDC_RP_VERIFICATION_CLIENT_SECRET')
     )
コード例 #2
0
ファイル: auth.py プロジェクト: cloudera/hue
    def verify_token(self, token, **kwargs):
        """Validate the token signature."""
        nonce = kwargs.get('nonce')

        token = force_bytes(token)
        if self.OIDC_RP_SIGN_ALGO.startswith('RS'):
            if self.OIDC_RP_IDP_SIGN_KEY is not None:
                key = self.OIDC_RP_IDP_SIGN_KEY
            else:
                key = self.retrieve_matching_jwk(token)
        else:
            key = self.OIDC_RP_CLIENT_SECRET

        # Verify the token
        verified_token = self._verify_jws(token, key)

        # The 'verified_token' will always be a byte string since it's
        # the result of base64.urlsafe_b64decode().
        # The payload is always the result of base64.urlsafe_b64decode().
        # In Python 3 and 2, that's always a byte string.
        # In Python3.6, the json.loads() function can accept a byte string
        # as it will automagically decode it to a unicode string before
        # deserializing https://bugs.python.org/issue17909
        verified_id = json.loads(verified_token.decode('utf-8'))
        token_nonce = verified_id.get('nonce')

        if import_from_settings('OIDC_USE_NONCE', True) and nonce != token_nonce:
            msg = 'JWT Nonce verification failed.'
            raise SuspiciousOperation(msg)
        return verified_id
コード例 #3
0
ファイル: views.py プロジェクト: cloudera/hue
    def login_success(self):
        auth.login(self.request, self.user)

        # Figure out when this id_token will expire. This is ignored unless you're
        # using the RenewIDToken middleware.
        expiration_interval = import_from_settings('OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS', 60 * 15)
        self.request.session['oidc_id_token_expiration'] = time.time() + expiration_interval

        return HttpResponseRedirect(self.success_url)
コード例 #4
0
ファイル: auth.py プロジェクト: cloudera/hue
    def get_token(self, payload):
        """Return token object as a dictionary."""

        response = requests.post(
            self.OIDC_OP_TOKEN_ENDPOINT,
            data=payload,
            verify=import_from_settings('OIDC_VERIFY_SSL', True))
        response.raise_for_status()
        return response.json()
コード例 #5
0
ファイル: auth.py プロジェクト: cloudera/hue
    def get_userinfo(self, access_token, id_token, verified_id):
        """Return user details dictionary. The id_token and verified_id are not used in
        the default implementation, but may be used when overriding this method"""

        user_response = requests.get(
            self.OIDC_OP_USER_ENDPOINT,
            headers={
                'Authorization': 'Bearer {0}'.format(access_token)
            },
            verify=import_from_settings('OIDC_VERIFY_SSL', True))
        user_response.raise_for_status()
        return user_response.json()
コード例 #6
0
ファイル: views.py プロジェクト: cloudera/hue
    def get(self, request):
        """OIDC client authentication initialization HTTP endpoint"""
        state = get_random_string(import_from_settings('OIDC_STATE_SIZE', 32))
        redirect_field_name = import_from_settings('OIDC_REDIRECT_FIELD_NAME', 'next')
        reverse_url = import_from_settings('OIDC_AUTHENTICATION_CALLBACK_URL',
                                           'oidc_authentication_callback')

        params = {
            'response_type': 'code',
            'scope': import_from_settings('OIDC_RP_SCOPES', 'openid email'),
            'client_id': self.OIDC_RP_CLIENT_ID,
            'redirect_uri': absolutify(
                request,
                reverse(reverse_url)
            ),
            'state': state,
        }

        params.update(self.get_extra_params(request))

        if import_from_settings('OIDC_USE_NONCE', True):
            nonce = get_random_string(import_from_settings('OIDC_NONCE_SIZE', 32))
            params.update({
                'nonce': nonce
            })
            request.session['oidc_nonce'] = nonce

        request.session['oidc_state'] = state
        request.session['oidc_login_next'] = get_next_url(request, redirect_field_name)

        query = urlencode(params)
        redirect_url = '{url}?{query}'.format(url=self.OIDC_OP_AUTH_ENDPOINT, query=query)
        return HttpResponseRedirect(redirect_url)
コード例 #7
0
ファイル: auth.py プロジェクト: cloudera/hue
    def get_or_create_user(self, access_token, id_token, verified_id):
        """Returns a User instance if 1 user is found. Creates a user if not found
        and configured to do so. Returns nothing if multiple users are matched."""

        session = self.request.session

        if import_from_settings('OIDC_STORE_ACCESS_TOKEN', False):
            session['oidc_access_token'] = access_token

        if import_from_settings('OIDC_STORE_ID_TOKEN', False):
            session['oidc_id_token'] = id_token

        # get userinfo
        user_info = self.get_userinfo(access_token, id_token, verified_id)

        email = user_info.get('email')

        claims_verified = self.verify_claims(user_info)
        if not claims_verified:
            LOGGER.debug('Login failed: Claims verification for %s failed.', email)
            return None

        # email based filtering
        users = self.filter_users_by_claims(user_info)

        if len(users) == 1:
            return self.update_user(users[0], user_info)
        elif len(users) > 1:
            # In the rare case that two user accounts have the same email address,
            # log and bail. Randomly selecting one seems really wrong.
            LOGGER.warn('Multiple users with email address %s.', email)
            return None
        elif import_from_settings('OIDC_CREATE_USER', True):
            user = self.create_user(user_info)
            return user
        else:
            LOGGER.debug('Login failed: No user with email %s found, and '
                         'OIDC_CREATE_USER is False', email)
            return None
コード例 #8
0
ファイル: views.py プロジェクト: yahkrivetko/mozillians
    def get(self, request):
        """OIDC client authentication initialization HTTP endpoint.

        This is based on the mozilla-django-oidc library
        """
        state = get_random_string(import_from_settings('OIDC_STATE_SIZE', 32))
        redirect_field_name = import_from_settings('OIDC_REDIRECT_FIELD_NAME', 'next')

        params = {
            'response_type': 'code',
            'scope': import_from_settings('OIDC_RP_SCOPES', 'openid email'),
            'client_id': self.OIDC_RP_VERIFICATION_CLIENT_ID,
            'redirect_uri': absolutify(
                request,
                nonprefixed_url('phonebook:verify_identity_callback')
            ),
            'state': state,
            'prompt': settings.OIDC_PROMPT
        }

        if import_from_settings('OIDC_USE_NONCE', True):
            nonce = get_random_string(import_from_settings('OIDC_NONCE_SIZE', 32))
            params.update({
                'nonce': nonce
            })
            request.session['oidc_verify_nonce'] = nonce

        # Add parameter to disable silent authentication and the LDAP check for AUTO_VOUCH_DOMAINS
        # This will allow users to verify AUTO_VOUCH_DOMAINS as contact identities
        params['account_linking'] = settings.OIDC_ACCOUNT_LINKING

        request.session['oidc_verify_state'] = state
        request.session['oidc_login_next'] = get_next_url(request, redirect_field_name)

        query = urlencode(params)
        redirect_url = '{url}?{query}'.format(url=self.OIDC_OP_AUTH_ENDPOINT, query=query)
        return HttpResponseRedirect(redirect_url)
コード例 #9
0
ファイル: middleware.py プロジェクト: cloudera/hue
    def process_request(self, request):
        if not self.is_refreshable_url(request):
            LOGGER.debug('request is not refreshable')
            return

        expiration = request.session.get('oidc_id_token_expiration', 0)
        now = time.time()
        if expiration > now:
            # The id_token is still valid, so we don't have to do anything.
            LOGGER.debug('id token is still valid (%s > %s)', expiration, now)
            return

        LOGGER.debug('id token has expired')
        # The id_token has expired, so we have to re-authenticate silently.
        auth_url = import_from_settings('OIDC_OP_AUTHORIZATION_ENDPOINT')
        client_id = import_from_settings('OIDC_RP_CLIENT_ID')
        state = get_random_string(import_from_settings('OIDC_STATE_SIZE', 32))

        # Build the parameters as if we were doing a real auth handoff, except
        # we also include prompt=none.
        params = {
            'response_type': 'code',
            'client_id': client_id,
            'redirect_uri': absolutify(
                request,
                reverse('oidc_authentication_callback')
            ),
            'state': state,
            'scope': import_from_settings('OIDC_RP_SCOPES', 'openid email'),
            'prompt': 'none',
        }

        if import_from_settings('OIDC_USE_NONCE', True):
            nonce = get_random_string(import_from_settings('OIDC_NONCE_SIZE', 32))
            params.update({
                'nonce': nonce
            })
            request.session['oidc_nonce'] = nonce

        request.session['oidc_state'] = state
        request.session['oidc_login_next'] = request.get_full_path()

        query = urlencode(params)
        redirect_url = '{url}?{query}'.format(url=auth_url, query=query)
        if request.is_ajax():
            # Almost all XHR request handling in client-side code struggles
            # with redirects since redirecting to a page where the user
            # is supposed to do something is extremely unlikely to work
            # in an XHR request. Make a special response for these kinds
            # of requests.
            # The use of 403 Forbidden is to match the fact that this
            # middleware doesn't really want the user in if they don't
            # refresh their session.
            response = JsonResponse({'refresh_url': redirect_url}, status=403)
            response['refresh_url'] = redirect_url
            return response
        return HttpResponseRedirect(redirect_url)
コード例 #10
0
ファイル: backend.py プロジェクト: cloudera/hue
  def logout(self, request, next_page):
    # https://stackoverflow.com/questions/46689034/logout-user-via-keycloak-rest-api-doesnt-work
    if request.session.get('_auth_user_backend', '') != 'desktop.auth.backend.OIDCBackend':
      return None

    session = request.session
    access_token = session.get('oidc_access_token', '')
    refresh_token = session.get('oidc_refresh_token', '')

    if access_token and refresh_token:
      oidc_logout_url = desktop.conf.OIDC.LOGOUT_REDIRECT_URL.get()
      client_id = import_from_settings('OIDC_RP_CLIENT_ID')
      client_secret = import_from_settings('OIDC_RP_CLIENT_SECRET')
      oidc_verify_ssl = import_from_settings('OIDC_VERIFY_SSL')
      form = {
        'client_id': client_id,
        'client_secret': client_secret,
        'refresh_token': refresh_token,
      }
      headers = {
        'Authorization': 'Bearer {0}'.format(access_token),
        'Content-Type': 'application/x-www-form-urlencoded'
      }
      resp = requests.post(oidc_logout_url, data=form, headers=headers, verify=oidc_verify_ssl)
      if resp.status_code >= 200 and resp.status_code < 300:
        LOG.debug("OpenID Connect logout succeed!")
        delete_oidc_session_tokens(session)
        auth.logout(request)
        response = HttpResponseRedirect(next_page)
        response.delete_cookie(LOAD_BALANCER_COOKIE)
        return response
      else:
        LOG.error("OpenID Connect logout failed: %s" % resp.content)
    else:
      LOG.warn("OpenID Connect tokens are not available, logout skipped!")
    return None
コード例 #11
0
ファイル: views.py プロジェクト: cloudera/hue
    def post(self, request):
        """Log out the user."""
        logout_url = self.redirect_url

        if is_authenticated(request.user):
            # Check if a method exists to build the URL to log out the user
            # from the OP.
            logout_from_op = import_from_settings('OIDC_OP_LOGOUT_URL_METHOD', '')
            if logout_from_op:
                logout_url = import_string(logout_from_op)(request)

            # Log out the Django user if they were logged in.
            auth.logout(request)

        return HttpResponseRedirect(logout_url)
コード例 #12
0
ファイル: auth.py プロジェクト: cloudera/hue
    def __init__(self, *args, **kwargs):
        """Initialize settings."""
        self.OIDC_OP_TOKEN_ENDPOINT = import_from_settings('OIDC_OP_TOKEN_ENDPOINT')
        self.OIDC_OP_USER_ENDPOINT = import_from_settings('OIDC_OP_USER_ENDPOINT')
        self.OIDC_OP_JWKS_ENDPOINT = import_from_settings('OIDC_OP_JWKS_ENDPOINT', None)
        self.OIDC_RP_CLIENT_ID = import_from_settings('OIDC_RP_CLIENT_ID')
        self.OIDC_RP_CLIENT_SECRET = import_from_settings('OIDC_RP_CLIENT_SECRET')
        self.OIDC_RP_SIGN_ALGO = import_from_settings('OIDC_RP_SIGN_ALGO', 'HS256')
        self.OIDC_RP_IDP_SIGN_KEY = import_from_settings('OIDC_RP_IDP_SIGN_KEY', None)

        if (self.OIDC_RP_SIGN_ALGO.startswith('RS') and
                (self.OIDC_RP_IDP_SIGN_KEY is None and self.OIDC_OP_JWKS_ENDPOINT is None)):
            msg = '{} alg requires OIDC_RP_IDP_SIGN_KEY or OIDC_OP_JWKS_ENDPOINT to be configured.'
            raise ImproperlyConfigured(msg.format(self.OIDC_RP_SIGN_ALGO))

        self.UserModel = get_user_model()
コード例 #13
0
ファイル: auth.py プロジェクト: cloudera/hue
    def create_user(self, claims):
        """Return object for a newly created user account."""
        # bluntly stolen from django-browserid
        # https://github.com/mozilla/django-browserid/blob/master/django_browserid/auth.py

        username_algo = import_from_settings('OIDC_USERNAME_ALGO', None)
        email = claims.get('email')
        if not email:
            return None

        if username_algo:
            if isinstance(username_algo, six.string_types):
                username_algo = import_string(username_algo)
            username = username_algo(email)
        else:
            username = default_username_algo(email)

        return self.UserModel.objects.create_user(username, email)
コード例 #14
0
ファイル: backend.py プロジェクト: cloudera/hue
  def create_user(self, claims):
    """Return object for a newly created user account."""
    # Overriding lib's logic, use preferred_username from oidc as username

    username = claims.get(import_from_settings('OIDC_USERNAME_ATTRIBUTE', 'preferred_username'), '')
    email = claims.get('email', '')
    first_name = claims.get('given_name', '')
    last_name = claims.get('family_name', '')

    if not username:
      if not email:
        LOG.debug("OpenID Connect no username and email while creating new user")
        return None
      username = default_username_algo(email)

    return self.UserModel.objects.create_user(username=username, email=email,
                                              first_name=first_name, last_name=last_name,
                                              is_superuser=self.is_hue_superuser(claims))
コード例 #15
0
ファイル: backend.py プロジェクト: cloudera/hue
  def authenticate(self, **kwargs):
    self.request = kwargs.pop('request', None)
    if not self.request:
      return None

    state = self.request.GET.get('state')
    code = self.request.GET.get('code')
    nonce = kwargs.pop('nonce', None)

    if not code or not state:
      return None

    reverse_url = import_from_settings('OIDC_AUTHENTICATION_CALLBACK_URL',
                                       'oidc_authentication_callback')

    token_payload = {
      'client_id': self.OIDC_RP_CLIENT_ID,
      'client_secret': self.OIDC_RP_CLIENT_SECRET,
      'grant_type': 'authorization_code',
      'code': code,
      'redirect_uri': absolutify(
        self.request,
        reverse(reverse_url)
      ),
    }

    # Get the token
    token_info = self.get_token(token_payload)
    id_token = token_info.get('id_token')
    access_token = token_info.get('access_token')
    refresh_token = token_info.get('refresh_token')

    # Validate the token
    verified_id = self.verify_token(id_token, nonce=nonce)

    if verified_id:
      self.save_refresh_tokens(refresh_token)
      user =  self.get_or_create_user(access_token, id_token, verified_id)
      user = rewrite_user(user)
      return user

    return None
コード例 #16
0
ファイル: auth.py プロジェクト: cloudera/hue
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the JWKS endpoint of the OP."""
        response_jwks = requests.get(
            self.OIDC_OP_JWKS_ENDPOINT,
            verify=import_from_settings('OIDC_VERIFY_SSL', True)
        )
        response_jwks.raise_for_status()
        jwks = response_jwks.json()

        # Compute the current header from the given token to find a match
        jws = JWS.from_compact(token)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        for jwk in jwks['keys']:
            if jwk['alg'] == smart_text(header.alg) and jwk['kid'] == smart_text(header.kid):
                key = jwk
        if key is None:
            raise SuspiciousOperation('Could not find a valid JWKS.')
        return key
コード例 #17
0
ファイル: middleware.py プロジェクト: cloudera/hue
    def exempt_urls(self):
        """Generate and return a set of url paths to exempt from SessionRefresh

        This takes the value of ``settings.OIDC_EXEMPT_URLS`` and appends three
        urls that mozilla-django-oidc uses. These values can be view names or
        absolute url paths.

        :returns: list of url paths (for example "/oidc/callback/")

        """
        exempt_urls = list(import_from_settings('OIDC_EXEMPT_URLS', []))
        exempt_urls.extend([
            'oidc_authentication_init',
            'oidc_authentication_callback',
            'oidc_logout',
        ])

        return set([
            url if url.startswith('/') else reverse(url)
            for url in exempt_urls
        ])
コード例 #18
0
ファイル: backend.py プロジェクト: sandredd/hue-1
    def save_refresh_tokens(self, refresh_token):
        session = self.request.session

        if import_from_settings('OIDC_STORE_REFRESH_TOKEN', False):
            session['oidc_refresh_token'] = refresh_token
コード例 #19
0
 def get_settings(attr, *args):
     return import_from_settings(attr, *args)
コード例 #20
0
 def test_attr_nonexisting_no_default_value(self):
     with self.assertRaises(ImproperlyConfigured):
         import_from_settings('EXAMPLE_VARIABLE')
コード例 #21
0
ファイル: views.py プロジェクト: cloudera/hue
 def get_extra_params(self, request):
     return import_from_settings('OIDC_AUTH_REQUEST_EXTRA_PARAMS', {})
コード例 #22
0
ファイル: views.py プロジェクト: cloudera/hue
 def failure_url(self):
     return import_from_settings('LOGIN_REDIRECT_URL_FAILURE', '/')
コード例 #23
0
def provider_logout(request):
    return get_from_well_known(
        utils.import_from_settings('OIDC_OP_METADATA_ENDPOINT'),
        'end_session_endpoint')
コード例 #24
0
ファイル: backend.py プロジェクト: cloudera/hue
  def save_refresh_tokens(self, refresh_token):
    session = self.request.session

    if import_from_settings('OIDC_STORE_REFRESH_TOKEN', False):
      session['oidc_refresh_token'] = refresh_token
コード例 #25
0
ファイル: backend.py プロジェクト: cloudera/hue
 def get_or_create_user(self, access_token, id_token, verified_id):
   user = super(OIDCBackend, self).get_or_create_user(access_token, id_token, verified_id)
   if not user and not import_from_settings('OIDC_CREATE_USER', True):
     # in this case, user is login from Keycloak, but not allow create
     self.logout(self.request, next_page=import_from_settings('LOGIN_REDIRECT_URL_FAILURE', '/'))
   return user
コード例 #26
0
ファイル: urls.py プロジェクト: cloudera/hue
from django.conf.urls import url
from django.utils.module_loading import import_string

from mozilla_django_oidc import views
from mozilla_django_oidc.utils import import_from_settings

DEFAULT_CALLBACK_CLASS = 'mozilla_django_oidc.views.OIDCAuthenticationCallbackView'
CALLBACK_CLASS_PATH = import_from_settings('OIDC_CALLBACK_CLASS', DEFAULT_CALLBACK_CLASS)

OIDCCallbackClass = import_string(CALLBACK_CLASS_PATH)


DEFAULT_AUTHENTICATE_CLASS = 'mozilla_django_oidc.views.OIDCAuthenticationRequestView'
AUTHENTICATE_CLASS_PATH = import_from_settings(
    'OIDC_AUTHENTICATE_CLASS', DEFAULT_AUTHENTICATE_CLASS
)

OIDCAuthenticateClass = import_string(AUTHENTICATE_CLASS_PATH)

urlpatterns = [
    url(r'^callback/$', OIDCCallbackClass.as_view(),
        name='oidc_authentication_callback'),
    url(r'^authenticate/$', OIDCAuthenticateClass.as_view(),
        name='oidc_authentication_init'),
    url(r'^logout/$', views.OIDCLogoutView.as_view(), name='oidc_logout'),
]
コード例 #27
0
    def authenticate(self, **kwargs):
        """Authenticates a user based on the OIDC code flow."""

        self.request = kwargs.pop('request', None)
        if not self.request:
            return None

        state = self.request.GET.get('state')
        code = self.request.GET.get('code')
        nonce = kwargs.pop('nonce', None)
        session = self.request.session

        if not code or not state:
            raise SuspiciousOperation('Code or state not found.')

        token_payload = {
            'client_id':
            self.OIDC_RP_CLIENT_ID,
            'client_secret':
            self.OIDC_RP_CLIENT_SECRET,
            'grant_type':
            'authorization_code',
            'code':
            code,
            'redirect_uri':
            absolutify(self.request, reverse('oidc_authentication_callback')),
        }

        # Get the token
        response = requests.post(self.OIDC_OP_TOKEN_ENDPOINT,
                                 data=token_payload,
                                 verify=import_from_settings(
                                     'OIDC_VERIFY_SSL', True))
        response.raise_for_status()

        # Validate the token
        token_response = response.json()
        id_token = token_response.get('id_token')
        if self.verify_token(id_token, nonce=nonce):
            access_token = token_response.get('access_token')

            if import_from_settings('OIDC_STORE_ACCESS_TOKEN', False):
                session['oidc_access_token'] = access_token

            if import_from_settings('OIDC_STORE_ID_TOKEN', False):
                session['oidc_id_token'] = id_token

            user_response = requests.get(
                self.OIDC_OP_USER_ENDPOINT,
                headers={'Authorization': 'Bearer {0}'.format(access_token)},
                verify=import_from_settings('OIDC_VERIFY_SSL', True))
            user_response.raise_for_status()

            user_info = user_response.json()
            email = user_info.get('email')

            claims_verified = self.verify_claims(user_info)
            if not claims_verified:
                LOGGER.debug(
                    'Login failed: Claims verification for %s failed.', email)
                return None

            # email based filtering
            users = self.filter_users_by_claims(user_info)

            if len(users) == 1:
                return self.update_user(users[0], user_info)
            elif len(users) > 1:
                # In the rare case that two user accounts have the same email address,
                # log and bail. Randomly selecting one seems really wrong.
                LOGGER.warn('Multiple users with email address %s.', email)
                return None
            elif import_from_settings('OIDC_CREATE_USER', True):
                user = self.create_user(user_info)
                return user
            else:
                LOGGER.debug(
                    'Login failed: No user with email %s found, and '
                    'OIDC_CREATE_USER is False', email)
                return None
        return None
コード例 #28
0
ファイル: backend.py プロジェクト: happydentist/hue
 def get_or_create_user(self, access_token, id_token, verified_id):
   user = super(OIDCBackend, self).get_or_create_user(access_token, id_token, verified_id)
   if not user and not import_from_settings('OIDC_CREATE_USER', True):
     # in this case, user is login from Keycloak, but not allow create
     self.logout(self.request, next_page=import_from_settings('LOGIN_REDIRECT_URL_FAILURE', '/'))
   return user
コード例 #29
0
ファイル: backend.py プロジェクト: happydentist/hue
 def filter_users_by_claims(self, claims):
   username = claims.get(import_from_settings('OIDC_USERNAME_ATTRIBUTE', 'preferred_username'))
   if not username:
     return self.UserModel.objects.none()
   return self.UserModel.objects.filter(username__iexact=username)
コード例 #30
0
 def get_extra_params(self, request):
     return import_from_settings('OIDC_AUTH_REQUEST_EXTRA_PARAMS', {})
コード例 #31
0
 def test_attr_nonexisting_default_value(self):
     s = import_from_settings('EXAMPLE_VARIABLE', 'example_default')
     self.assertEqual(s, 'example_default')
コード例 #32
0
ファイル: views.py プロジェクト: johngian/mozillians
 def __init__(self, *args, **kwargs):
     """Override the init method to dynamically pass a different client_id."""
     self.OIDC_RP_VERIFICATION_CLIENT_ID = (
         import_from_settings('OIDC_RP_VERIFICATION_CLIENT_ID')
     )
     super(VerifyIdentityView, self).__init__(*args, **kwargs)
コード例 #33
0
ファイル: backend.py プロジェクト: cloudera/hue
 def filter_users_by_claims(self, claims):
   username = claims.get(import_from_settings('OIDC_USERNAME_ATTRIBUTE', 'preferred_username'))
   if not username:
     return self.UserModel.objects.none()
   return self.UserModel.objects.filter(username__iexact=username)
コード例 #34
0
ファイル: views.py プロジェクト: johngian/mozillians
    def get(self, request):
        """Callback handler for OIDC authorization code flow.

        This is based on the mozilla-django-oidc library.
        This callback is used to verify the identity added by the user.
        Users are already logged in and we do not care about authentication.
        The JWT token is used to prove the identity of the user.
        """

        profile = request.user.userprofile
        # This is a difference nonce than the one used to login!
        nonce = request.session.get('oidc_verify_nonce')
        if nonce:
            # Make sure that nonce is not used twice
            del request.session['oidc_verify_nonce']

        # Check for all possible errors and display a message to the user.
        errors = [
            'code' not in request.GET,
            'state' not in request.GET,
            'oidc_verify_state' not in request.session,
            request.GET['state'] != request.session['oidc_verify_state']
        ]
        if any(errors):
            msg = 'Something went wrong, account verification failed.'
            messages.error(request, msg)
            return redirect('phonebook:profile_edit')

        token_payload = {
            'client_id': self.OIDC_RP_VERIFICATION_CLIENT_ID,
            'client_secret': self.OIDC_RP_VERIFICATION_CLIENT_SECRET,
            'grant_type': 'authorization_code',
            'code': request.GET['code'],
            'redirect_uri': absolutify(
                self.request,
                nonprefixed_url('phonebook:verify_identity_callback')
            ),
        }
        response = requests.post(self.OIDC_OP_TOKEN_ENDPOINT,
                                 data=token_payload,
                                 verify=import_from_settings('OIDC_VERIFY_SSL', True))
        response.raise_for_status()
        token_response = response.json()
        id_token = token_response.get('id_token')

        # Verify JWT
        jws = JWS.from_compact(force_bytes(id_token))
        jwk = JWK.load(smart_bytes(self.OIDC_RP_VERIFICATION_CLIENT_SECRET))
        verified_token = None
        if jws.verify(jwk):
            verified_token = jws.payload

        # Create the new Identity Profile.
        if verified_token:
            user_info = json.loads(verified_token)
            email = user_info['email']

            if not user_info.get('email_verified'):
                msg = 'Account verification failed: Email is not verified.'
                messages.error(request, msg)
                return redirect('phonebook:profile_edit')

            user_q = {
                'auth0_user_id': user_info['sub'],
                'email': email
            }

            # If we are linking GitHub we need to save
            # the username too.
            if 'github|' in user_info['sub']:
                user_q['username'] = user_info['nickname']

            # Check that the identity doesn't exist in another Identity profile
            # or in another mozillians profile
            error_msg = ''
            if IdpProfile.objects.filter(**user_q).exists():
                error_msg = 'Account verification failed: Identity already exists.'
            elif User.objects.filter(email__iexact=email).exclude(pk=profile.user.pk).exists():
                error_msg = 'The email in this identity is used by another user.'
            if error_msg:
                messages.error(request, error_msg)
                next_url = self.request.session.get('oidc_login_next', None)
                return HttpResponseRedirect(next_url or reverse('phonebook:profile_edit'))

            # Save the new identity to the IdpProfile
            user_q['profile'] = profile
            idp, created = IdpProfile.objects.get_or_create(**user_q)

            current_idp = get_object_or_none(IdpProfile, profile=profile, primary=True)
            # The new identity is stronger than the one currently used. Let's swap
            append_msg = ''
            # We need to check for equality too in the case a user updates the primary email in
            # the same identity (matching auth0_user_id). If there is an addition of the same type
            # we are not swapping login identities
            if ((current_idp and current_idp.type < idp.type) or
                (current_idp and current_idp.auth0_user_id == idp.auth0_user_id) or
                    (not current_idp and created and idp.type >= IdpProfile.PROVIDER_GITHUB)):
                IdpProfile.objects.filter(profile=profile).exclude(pk=idp.pk).update(primary=False)
                idp.primary = True
                idp.save()
                # Also update the primary email of the user
                update_email_in_basket(profile.user.email, idp.email)
                User.objects.filter(pk=profile.user.id).update(email=idp.email)
                append_msg = ' You need to use this identity the next time you will login.'

            send_userprofile_to_cis.delay(profile.pk)
            if created:
                msg = 'Account successfully verified.'
                if append_msg:
                    msg += append_msg
                messages.success(request, msg)
            else:
                msg = 'Account verification failed: Identity already exists.'
                messages.error(request, msg)

        next_url = self.request.session.get('oidc_login_next', None)
        return HttpResponseRedirect(next_url or reverse('phonebook:profile_edit'))
コード例 #35
0
from django.conf.urls import url
from django.utils.module_loading import import_string

from mozilla_django_oidc import views
from mozilla_django_oidc.utils import import_from_settings

DEFAULT_CALLBACK_CLASS = 'mozilla_django_oidc.views.OIDCAuthenticationCallbackView'
CALLBACK_CLASS_PATH = import_from_settings('OIDC_CALLBACK_CLASS',
                                           DEFAULT_CALLBACK_CLASS)

OIDCCallbackClass = import_string(CALLBACK_CLASS_PATH)

DEFAULT_AUTHENTICATE_CLASS = 'mozilla_django_oidc.views.OIDCAuthenticationRequestView'
AUTHENTICATE_CLASS_PATH = import_from_settings('OIDC_AUTHENTICATE_CLASS',
                                               DEFAULT_AUTHENTICATE_CLASS)

OIDCAuthenticateClass = import_string(AUTHENTICATE_CLASS_PATH)

urlpatterns = [
    url(r'^callback/$',
        OIDCCallbackClass.as_view(),
        name='oidc_authentication_callback'),
    url(r'^authenticate/$',
        OIDCAuthenticateClass.as_view(),
        name='oidc_authentication_init'),
    url(r'^logout/$', views.OIDCLogoutView.as_view(), name='oidc_logout'),
]
コード例 #36
0
    def __init__(self, *args, **kwargs):
        super(OIDCAuthenticationRequestView, self).__init__(*args, **kwargs)

        self.OIDC_OP_AUTH_ENDPOINT = get_from_well_known(
            utils.import_from_settings('OIDC_OP_METADATA_ENDPOINT'),
            'authorization_endpoint')
コード例 #37
0
 def success_url(self):
     next_url = self.request.session.get('oidc_login_next', None)
     return next_url or import_from_settings('LOGIN_REDIRECT_URL', '/')
コード例 #38
0
 def failure_url(self):
     return import_from_settings('LOGIN_REDIRECT_URL_FAILURE', '/')
コード例 #39
0
ファイル: views.py プロジェクト: cloudera/hue
 def success_url(self):
     # Pull the next url from the session or settings--we don't need to
     # sanitize here because it should already have been sanitized.
     next_url = self.request.session.get('oidc_login_next', None)
     return next_url or import_from_settings('LOGIN_REDIRECT_URL', '/')
コード例 #40
0
 def success_url(self):
     # Pull the next url from the session or settings--we don't need to
     # sanitize here because it should already have been sanitized.
     next_url = self.request.session.get('oidc_login_next', None)
     return next_url or import_from_settings('LOGIN_REDIRECT_URL', '/')
コード例 #41
0
from django.conf.urls import url
from django.utils.module_loading import import_string

from mozilla_django_oidc import views
from mozilla_django_oidc.utils import import_from_settings

DEFAULT_CALLBACK_CLASS = 'mozilla_django_oidc.views.OIDCAuthenticationCallbackView'
CALLBACK_CLASS_PATH = import_from_settings('OIDC_CALLBACK_CLASS',
                                           DEFAULT_CALLBACK_CLASS)

OIDCCallbackClass = import_string(CALLBACK_CLASS_PATH)

urlpatterns = [
    url(r'^callback/$',
        OIDCCallbackClass.as_view(),
        name='oidc_authentication_callback'),
    url(r'^authenticate/$',
        views.OIDCAuthenticationRequestView.as_view(),
        name='oidc_authentication_init'),
    url(r'^logout/$', views.OIDCLogoutView.as_view(), name='oidc_logout'),
]
コード例 #42
0
ファイル: views.py プロジェクト: Oyelakin-Mercy/mozillians
 def __init__(self, *args, **kwargs):
     """Override the init method to dynamically pass a different client_id."""
     self.OIDC_RP_VERIFICATION_CLIENT_ID = (
         import_from_settings('OIDC_RP_VERIFICATION_CLIENT_ID'))
     super(VerifyIdentityView, self).__init__(*args, **kwargs)
コード例 #43
0
ファイル: views.py プロジェクト: cloudera/hue
 def redirect_url(self):
     """Return the logout url defined in settings."""
     return import_from_settings('LOGOUT_REDIRECT_URL', '/')
コード例 #44
0
ファイル: views.py プロジェクト: Oyelakin-Mercy/mozillians
    def get(self, request):
        """Callback handler for OIDC authorization code flow.

        This is based on the mozilla-django-oidc library.
        This callback is used to verify the identity added by the user.
        Users are already logged in and we do not care about authentication.
        The JWT token is used to prove the identity of the user.
        """

        profile = request.user.userprofile
        # This is a difference nonce than the one used to login!
        nonce = request.session.get('oidc_verify_nonce')
        if nonce:
            # Make sure that nonce is not used twice
            del request.session['oidc_verify_nonce']

        # Check for all possible errors and display a message to the user.
        errors = [
            'code' not in request.GET, 'state' not in request.GET,
            'oidc_verify_state' not in request.session,
            not request.GET.get('state')
            or request.GET['state'] != request.session['oidc_verify_state']
        ]
        if any(errors):
            msg = 'Something went wrong, account verification failed.'
            messages.error(request, msg)
            return redirect('phonebook:profile_edit')

        token_payload = {
            'client_id':
            self.OIDC_RP_VERIFICATION_CLIENT_ID,
            'client_secret':
            self.OIDC_RP_VERIFICATION_CLIENT_SECRET,
            'grant_type':
            'authorization_code',
            'code':
            request.GET['code'],
            'redirect_uri':
            absolutify(self.request,
                       nonprefixed_url('phonebook:verify_identity_callback')),
        }
        response = requests.post(self.OIDC_OP_TOKEN_ENDPOINT,
                                 data=token_payload,
                                 verify=import_from_settings(
                                     'OIDC_VERIFY_SSL', True))
        response.raise_for_status()
        token_response = response.json()
        id_token = token_response.get('id_token')

        # Verify JWT
        jws = JWS.from_compact(force_bytes(id_token))
        jwk = JWK.load(smart_bytes(self.OIDC_RP_VERIFICATION_CLIENT_SECRET))
        verified_token = None
        if jws.verify(jwk):
            verified_token = jws.payload

        # Create the new Identity Profile.
        if verified_token:
            user_info = json.loads(verified_token)
            email = user_info['email']
            verification_user_id = user_info.get(ORIGINAL_CONNECTION_USER_ID)
            msg = ''

            if not user_info.get('email_verified'):
                msg = 'Account verification failed: Email is not verified.'

            if not verification_user_id:
                msg = 'Account verification failed: Could not get original user id'

            if msg:
                messages.error(request, msg)
                return redirect('phonebook:profile_edit')

            user_q = {'auth0_user_id': verification_user_id, 'email': email}

            # If we are linking GitHub we need to save
            # the username too.
            if 'github|' in verification_user_id:
                user_q['username'] = user_info['nickname']

            # Check that the identity doesn't exist in another Identity profile
            # or in another mozillians profile
            error_msg = ''
            if IdpProfile.objects.filter(**user_q).exists():
                error_msg = 'Account verification failed: Identity already exists.'
            elif User.objects.filter(email__iexact=email).exclude(
                    pk=profile.user.pk).exists():
                error_msg = 'The email in this identity is used by another user.'
            if error_msg:
                messages.error(request, error_msg)
                next_url = self.request.session.get('oidc_login_next', None)
                return HttpResponseRedirect(
                    next_url or reverse('phonebook:profile_edit'))

            # Save the new identity to the IdpProfile
            user_q['profile'] = profile
            idp, created = IdpProfile.objects.get_or_create(**user_q)

            current_idp = get_object_or_none(IdpProfile,
                                             profile=profile,
                                             primary=True)
            # The new identity is stronger than the one currently used. Let's swap
            append_msg = ''
            # We need to check for equality too in the case a user updates the primary email in
            # the same identity (matching auth0_user_id). If there is an addition of the same type
            # we are not swapping login identities
            if ((current_idp and current_idp.type < idp.type)
                    or (current_idp
                        and current_idp.auth0_user_id == idp.auth0_user_id)
                    or (not current_idp and created
                        and idp.type >= IdpProfile.PROVIDER_GITHUB)):
                IdpProfile.objects.filter(profile=profile).exclude(
                    pk=idp.pk).update(primary=False)
                idp.primary = True
                idp.save()
                # Also update the primary email of the user
                update_email_in_basket(profile.user.email, idp.email)
                User.objects.filter(pk=profile.user.id).update(email=idp.email)
                append_msg = ' You need to use this identity the next time you will login.'

            send_userprofile_to_cis.delay(profile.pk)
            if created:
                msg = 'Account successfully verified.'
                if append_msg:
                    msg += append_msg
                messages.success(request, msg)
            else:
                msg = 'Account verification failed: Identity already exists.'
                messages.error(request, msg)

        next_url = self.request.session.get('oidc_login_next', None)
        return HttpResponseRedirect(next_url
                                    or reverse('phonebook:profile_edit'))
コード例 #45
0
    def __init__(self, *args, **kwargs):
        super(OIDCAuthenticationRequestView, self).__init__(*args, **kwargs)

        self.OIDC_OP_AUTH_ENDPOINT = import_from_settings('OIDC_OP_AUTHORIZATION_ENDPOINT')
        self.OIDC_RP_CLIENT_ID = import_from_settings('OIDC_RP_CLIENT_ID')
コード例 #46
0
    def process_request(self, request):
        site = request.site
        if not hasattr(site, "oidcsettings"):
            raise RuntimeError(
                "Site {} has no settings configured.".format(site))

        if not self.is_refreshable_url(request):
            return

        expiration = request.session.get('oidc_id_token_expiration', 0)
        now = time.time()
        if expiration > now:
            # The id_token is still valid, so we don't have to do anything.
            return

        # The id_token has expired, so we have to re-authenticate silently.
        auth_url = import_from_settings('OIDC_OP_AUTHORIZATION_ENDPOINT')
        client_id = site.oidcsettings.oidc_rp_client_id
        state = get_random_string(import_from_settings('OIDC_STATE_SIZE', 32))

        # Build the parameters as if we were doing a real auth handoff, except
        # we also include prompt=none.
        params = {
            'response_type':
            'code',
            'client_id':
            client_id,
            'redirect_uri':
            absolutify(request, reverse('oidc_authentication_callback')),
            'state':
            state,
            'scope':
            site.oidcsettings.oidc_rp_scopes,
            'prompt':
            'none',
        }

        if import_from_settings('OIDC_USE_NONCE', True):
            nonce = get_random_string(
                import_from_settings('OIDC_NONCE_SIZE', 32))
            params.update({'nonce': nonce})
            request.session['oidc_nonce'] = nonce

        request.session['oidc_state'] = state
        request.session['oidc_login_next'] = request.get_full_path()

        query = urlencode(params)
        redirect_url = '{url}?{query}'.format(url=auth_url, query=query)
        if request.is_ajax():
            # Almost all XHR request handling in client-side code struggles
            # with redirects since redirecting to a page where the user
            # is supposed to do something is extremely unlikely to work
            # in an XHR request. Make a special response for these kinds
            # of requests.
            # The use of 403 Forbidden is to match the fact that this
            # middleware doesn't really want the user in if they don't
            # refresh their session.
            response = JsonResponse({'refresh_url': redirect_url}, status=403)
            response['refresh_url'] = redirect_url
            return response
        return HttpResponseRedirect(redirect_url)
コード例 #47
0
 def redirect_url(self):
     """Return the logout url defined in settings."""
     return import_from_settings('LOGOUT_REDIRECT_URL', '/')
コード例 #48
0
ファイル: views.py プロジェクト: cloudera/hue
    def __init__(self, *args, **kwargs):
        super(OIDCAuthenticationRequestView, self).__init__(*args, **kwargs)

        self.OIDC_OP_AUTH_ENDPOINT = import_from_settings('OIDC_OP_AUTHORIZATION_ENDPOINT')
        self.OIDC_RP_CLIENT_ID = import_from_settings('OIDC_RP_CLIENT_ID')