def ensure_valid( strategy, backend, user, registering_user, weblate_action, weblate_expires, new_association, details, **kwargs, ): """Ensure the activation link is still.""" # Didn't the link expire? if weblate_expires < time.time(): raise AuthMissingParameter(backend, "expires") # We allow password reset for unauthenticated users if weblate_action == "reset": if strategy.request.user.is_authenticated: messages.warning( strategy.request, _("You can not complete password reset while signed in."), ) messages.warning(strategy.request, _("The registration link has been invalidated.")) raise AuthMissingParameter(backend, "user") return # Add e-mail/register should stay on same user if user and user.is_authenticated: current_user = user.pk else: current_user = None if current_user != registering_user: if registering_user is None: messages.warning( strategy.request, _("You can not complete registration while signed in."), ) else: messages.warning( strategy.request, _("You can confirm your registration only while signed in."), ) messages.warning(strategy.request, _("The registration link has been invalidated.")) raise AuthMissingParameter(backend, "user") # Verify if this mail is not used on other accounts if new_association: same = VerifiedEmail.objects.filter(email__iexact=details["email"]) if user: same = same.exclude(social__user=user) if same.exists(): AuditLog.objects.create(same[0].social.user, strategy.request, "connect") raise EmailAlreadyAssociated(backend, "E-mail exists")
def verify_data(self, response): bot_token = self.setting('BOT_TOKEN') if bot_token is None: raise AuthMissingParameter('telegram', 'SOCIAL_AUTH_TELEGRAM_BOT_TOKEN') received_hash_string = response.get('hash') auth_date = response.get('auth_date') if received_hash_string is None or auth_date is None: raise AuthMissingParameter('telegram', 'hash or auth_date') data_check_string = [ '{}={}'.format(k, v) for k, v in response.items() if k != 'hash' ] data_check_string = '\n'.join(sorted(data_check_string)) secret_key = hashlib.sha256(bot_token.encode()).digest() built_hash = hmac.new(secret_key, msg=data_check_string.encode(), digestmod=hashlib.sha256).hexdigest() current_timestamp = int(time.time()) auth_timestamp = int(auth_date) if current_timestamp - auth_timestamp > 86400: raise AuthFailed('telegram', 'Auth date is outdated') if built_hash != received_hash_string: raise AuthFailed('telegram', 'Invalid hash supplied')
def verify_open(strategy, backend, user, weblate_action, **kwargs): """Check whether it is possible to create new user.""" # Check whether registration is open if (not user and not settings.REGISTRATION_OPEN and weblate_action not in ('reset', 'remove', 'invite')): raise AuthMissingParameter(backend, 'disabled') # Ensure it's still same user request = strategy.request if request.user.pk != request.session.get('social_auth_user'): raise AuthMissingParameter(backend, 'user')
def verify_open(strategy, backend, user=None, **kwargs): """Check whether it is possible to create new user.""" # Check whether registration is open if not user and not settings.REGISTRATION_OPEN: raise AuthMissingParameter(backend, 'disabled') # Avoid adding associations to demo user if user and settings.DEMO_SERVER and user.username == 'demo': raise AuthMissingParameter(backend, 'demo') # Ensure it's still same user request = strategy.request if request.user.pk != request.session.get('social_auth_user'): raise AuthMissingParameter(backend, 'user')
def verify_open(strategy, backend, user, weblate_action, **kwargs): """Check whether it is possible to create new user.""" # Check whether registration is open if (not user and not settings.REGISTRATION_OPEN and weblate_action not in ("reset", "remove", "invite")): raise AuthMissingParameter(backend, "disabled") # Ensure it's still same user (if sessions was kept as this is to avoid # completing authentication under diferent user than initiated it, with # new session, it will complete as new user) current_user = strategy.request.user.pk init_user = strategy.request.session.get("social_auth_user") if strategy.request.session.session_key and current_user != init_user: raise AuthMissingParameter(backend, "user")
def ensure_valid(strategy, backend, user, registering_user, weblate_action, weblate_expires, new_association, details, **kwargs): """Ensure the activation link is still.""" # Didn't the link expire? if weblate_expires < time.time(): raise AuthMissingParameter(backend, 'expires') # We allow password reset for unauthenticated users if weblate_action == 'reset': if strategy.request.user.is_authenticated: messages.warning( strategy.request, _('You can not complete password reset while logged in!')) messages.warning(strategy.request, _('The registration link has been invalidated.')) raise AuthMissingParameter(backend, 'user') return # Add email/register should stay on same user if user and user.is_authenticated: current_user = user.pk else: current_user = None if current_user != registering_user: if registering_user is None: messages.warning( strategy.request, _('You can not complete registration while logged in!')) else: messages.warning( strategy.request, _('You can confirm your registration only while logged in!')) messages.warning(strategy.request, _('The registration link has been invalidated.')) raise AuthMissingParameter(backend, 'user') # Verify if this mail is not used on other accounts if new_association: same = VerifiedEmail.objects.filter(email=details['email']) if user: same = same.exclude(social__user=user) if same.exists(): notify_account_activity(same[0].social.user, strategy.request, 'connect') raise AuthAlreadyAssociated(backend, 'Email exists')
def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" if 'connectData' not in self.data: raise AuthMissingParameter(self, self.ID_KEY) data = json.loads(base64.b64decode(self.data['connectData']).decode('utf-8')) kwargs.update({'response': data, 'backend': self}) return self.strategy.authenticate(*args, **kwargs)
def require_email(backend, details, weblate_action, user=None, is_new=False, **kwargs): """Force entering email for backends which don't provide it.""" if backend.name == 'github': email = get_github_email(kwargs['response']['access_token']) if email is not None: details['email'] = email # Remove any pending email validation codes if details.get('email') and backend.name == 'email': invalidate_reset_codes(emails=(details['email'], )) # Remove all account reset codes if user and weblate_action == 'reset': invalidate_reset_codes(user=user) if user and user.email: # Force validation of new email address if backend.name == 'email': return {'is_new': True} return None elif is_new and not details.get('email'): raise AuthMissingParameter(backend, 'email') return None
def require_email(backend, details, weblate_action, user=None, is_new=False, **kwargs): """Force entering e-mail for backends which don't provide it.""" if backend.name == "github": email = get_github_email(kwargs["response"]["access_token"]) if email is not None: details["email"] = email if details.get("email", "").endswith("@users.noreply.github.com"): del details["email"] # Remove any pending e-mail validation codes if details.get("email") and backend.name == "email": invalidate_reset_codes(emails=(details["email"], )) # Remove all account reset codes if user and weblate_action == "reset": invalidate_reset_codes(user=user) if user and user.email: # Force validation of new e-mail address if backend.name == "email": return {"is_new": True} return None if is_new and not details.get("email"): raise AuthMissingParameter(backend, "email") return None
def validate_state(self): """Validate state value. Raises exception on error, returns state value if valid.""" if not self.STATE_PARAMETER and not self.REDIRECT_STATE: return None state = self.get_session_state() request_state = self.get_request_state() #self.social_error_logger('Test Error Message.') if not request_state: self.errordesc = AuthMissingParameter(self, 'state').__str__() self.social_error_logger(self.errordesc) #raise AuthMissingParameter(self, 'state') elif not state: self.errordesc = 'Session value state missing.' self.social_error_logger(self.errordesc) #raise AuthStateMissing(self, 'state') # name = self.name + '_state' # state = self.data['state'] # self.strategy.session_set(name, state) # state = self.get_session_state() # if not state: # raise AuthStateMissing(self, 'state') # else: return state elif not request_state == state: self.errordesc = 'Wrong state parameter given' self.social_error_logger(self.errordesc) #raise AuthStateForbidden(self) else: return state
def auth_complete(self, *args, **kwargs): borrower_info = kwargs.get('borrower_info') if not borrower_info: raise AuthMissingParameter(self, 'borrower_info') kwargs.update({'response': borrower_info, 'backend': self}) return self.strategy.authenticate(*args, **kwargs)
def store_email(strategy, backend, user, social, details, **kwargs): """Store verified email.""" if 'email' not in details or details['email'] is None: raise AuthMissingParameter(backend, 'email') verified, dummy = VerifiedEmail.objects.get_or_create(social=social) if verified.email != details['email']: verified.email = details['email'] verified.save()
def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" if 'connectData' not in self.data: raise AuthMissingParameter(self, 'connectData') response = json.loads(base64.b64decode(self.data['connectData']).decode('utf-8')) kwargs.update({'response': response, 'backend': self}) return self.do_auth(response['token'], *args, **kwargs)
def auth_complete(self, *args, **kwargs): if 'SAMLResponse' not in self.data: raise AuthMissingParameter(self, 'SAMLResponse') if 'RelayState' not in self.data: raise AuthMissingParameter(self, 'RelayState') nonce = self.data['RelayState'] nonce_obj = self.get_nonce(nonce) if not nonce_obj: raise AuthStateForbidden(self, 'Invalid nonce in RelayState: %s' % nonce) # Remove the association object to prevent re-use attacks. self.remove_nonce(nonce_obj.id) request = self.strategy.request if not request.session.session_key and nonce_obj.secret: session = session_engine.SessionStore(nonce_obj.secret) request.session = self.strategy.session = session # FIXME: verify AuthCode resp = base64.b64decode(self.data['SAMLResponse']).decode('utf8') data = json.loads(resp) status_code = data.get('status_code', '') if status_code.lower() != 'success': auditlog.log_authentication_failure(request, self.name) raise AuthFailed(self, 'Authentication unsuccessful: %s' % status_code) oid = data.get('oid', '') if not oid: raise AuthMissingParameter(self, 'oid') auditlog.log_authentication_success(request, self.name, identifier=oid) response = { 'oid': oid, 'attributes': data.get('attributes', {}), 'session_index': data.get('session_index', ''), } kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs)
def openid_url(self): """Return service provider URL. This base class is generic accepting a POST parameter that specifies provider URL.""" if self.URL: return self.URL elif OPENID_ID_FIELD in self.data: return self.data[OPENID_ID_FIELD] elif self.strategy.session_get(OPENID_ID_FIELD, None): return self.strategy.session_get(OPENID_ID_FIELD, None) else: raise AuthMissingParameter(self, OPENID_ID_FIELD)
def ensure_valid(strategy, backend, user, registering_user, weblate_action, weblate_expires, **kwargs): """Ensure the activation link is still.""" # Didn't the link expire? if weblate_expires < time.time(): raise AuthMissingParameter(backend, 'expires') # We allow password reset for unauthenticated users if weblate_action == 'reset': if strategy.request.user.is_authenticated: messages.warning( strategy.request, _('You can not complete password reset while logged in!')) messages.warning(strategy.request, _('The registration link has been invalidated.')) raise AuthMissingParameter(backend, 'user') return # Add email/register should stay on same user if user and user.is_authenticated: current_user = user.pk else: current_user = None if current_user != registering_user: if registering_user is None: messages.warning( strategy.request, _('You can not complete registration while logged in!')) else: messages.warning( strategy.request, _('You can confirm your registration only while logged in!')) messages.warning(strategy.request, _('The registration link has been invalidated.')) raise AuthMissingParameter(backend, 'user')
def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" if self.ENV_USERNAME in os.environ: response = os.environ elif type( self.strategy ).__name__ == "DjangoStrategy" and self.ENV_USERNAME in self.strategy.request.META: # Looks like the Django strategy. In this case, it might by mod_wsgi, which stores # authentication environment variables in request.META response = self.strategy.request.META else: raise AuthMissingParameter( self, "%s, found only: %s" % (self.ENV_USERNAME, str(os.environ))) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs)
def auth_complete(self, *args, **kwargs): """ Completes loging process, must return user instance. """ if self.ID_KEY not in self.data: code = (self.strategy.request.GET.get('verification_code') or self.strategy.request.POST.get('verification_code')) code_object = CustomCode.objects.filter(code=code, verified=False).first() if code_object: email = code_object.email self.data.update({'email': email}) if code_object.next_page: self.data['next'] = code_object.next_page self.strategy.session_set('next', code_object.next_page) else: raise AuthMissingParameter(self, self.ID_KEY) kwargs.update({'response': self.data, 'backend': self}) return self.strategy.authenticate(*args, **kwargs)
def _get_attr(self, response_attributes: Dict[str, Any], attribute_names: List[str], optional: bool = False) -> str: """ Fetches a specific attribute from the SAML response, attempting with multiple different attribute names. We attempt multiple attribute names to make it easier for admins to configure SAML (less configuration to set). """ output = None for _attr in attribute_names: if _attr in response_attributes: output = response_attributes[_attr] break if not output and not optional: raise AuthMissingParameter("saml", attribute_names[0]) if isinstance(output, list): output = output[0] return output
def auth_url(self): """ Overridden to use the config from the relevant OrganizationDomain Get the URL to which we must redirect in order to authenticate the user """ email = self.strategy.request_data().get("email") if not email: raise AuthMissingParameter("saml", "email") instance = OrganizationDomain.objects.get_verified_for_email_address( email=email) if not instance or not instance.has_saml: raise AuthFailed("saml", "SAML not configured for this user.") auth = self._create_saml_auth(idp=self.get_idp(instance)) # Below, return_to sets the RelayState, which contains the ID of # the `OrganizationDomain`. We use it to store the specific SAML IdP # name, since we multiple IdPs share the same auth_complete URL. return auth.login(return_to=str(instance.id))
def get_user_id(self, *args, **kwargs): """ Insert a slug at the beginning of the user id. Retroactively add the slug for social users that don't have one. The slug must be defined in the 'OTHER_SETTINGS' field from the provider configuration. In case a slug was not defined raise an exception due to misconfigured backend. This three behaviours can be controlled via the settings: - 'SOCIAL_AUTH_NAMESPACED_UIDS': if false behave as originally intended. If true, add a slug to the uid. - 'SOCIAL_AUTH_ALLOW_SLUGLESS_UID': if false raise and exception when no slug is defined. If true, return de original uid when no slug is defined. - 'SOCIAL_AUTH_ALLOW_WRITE_SLUG_UID': If true update the uid of existing social users if any. A first step to start migrating to name spaced uids would look like this: on the site settings: { "SOCIAL_AUTH_NAMESPACED_UIDS": true, "SOCIAL_AUTH_ALLOW_SLUGLESS_UID": true, "SOCIAL_AUTH_ALLOW_WRITE_SLUG_UID": true } on the provider configuration: OTHER_SETTINGS: { "slug": "myslug" } Setting 'SOCIAL_AUTH_NAMESPACED_UIDS' to true and adding the slug to 'OTHER_SETTINGS' will create new associations with the new format. 'SOCIAL_AUTH_ALLOW_WRITE_SLUG_UID'=true will update older entries to the new format at login time and 'SOCIAL_AUTH_ALLOW_SLUGLESS_UID'=true would allow the previous format for older entries for the time being. Once all the old uids have been updated to the new format we can forbid the old format altogether and stop trying to updated uids without a slug. on the site settings: { "SOCIAL_AUTH_NAMESPACED_UIDS": true, "SOCIAL_AUTH_ALLOW_SLUGLESS_UID": false, "SOCIAL_AUTH_ALLOW_WRITE_SLUG_UID": false } on the provider configuration: OTHER_SETTINGS: { "slug": "myslug" } """ uid = super().get_user_id(*args, **kwargs) namespaced_uids = self.setting('NAMESPACED_UIDS', False) if not namespaced_uids: return uid strategy = self.strategy slug = strategy.setting('slug', backend=self) provider = self.name allow_slugless_uid = self.setting('ALLOW_SLUGLESS_UID', False) allow_write_slug_uid = self.setting('ALLOW_WRITE_SLUG_UID', True) if not slug: if allow_slugless_uid: return uid raise AuthMissingParameter(self, 'slug') slug_uid = '{0}:{1}'.format(slug, uid) if allow_write_slug_uid: social = strategy.storage.user.get_social_auth(provider, uid) if social: social.uid = slug_uid social.save() LOG.info("Updating uid: %s to %s", uid, slug_uid) return slug_uid