def __enter__(self): try: res, self.context = kerberos.authGSSServerInit(self.serviceType) except kerberos.KrbError as e: msg = repr(e) log.msg(msg) raise LoginFailed(msg) if res < 0: raise LoginFailed() return self
def decode(self, response, request): """Parse the Authorization header and also extract the OAuth token. The authorization response must contain a base64-encoded, colon-separated username and password. The response parsing code below is from C{twisted.web._auth.basic.BasicCredentialFactory} (as opposed to using the more modern base64 module). We also look for the C{X-FluidDB-Access-Token} header. OAuth2 requests must be made over a secure transport. @param response: The value of the HTTP Authorization header. If this is empty this is an anonymous 'anon' user request. @param request: The incoming HTTP request. @raise LoginFailed: If the Authorization header is not valid base64, or if the Authorization header cannot be split into 2 pieces by ':', or if the request is not via https, or if the C{X-FluidDB-Access-Token} header is not present or empty. @return: an L{OAuth2Credentials} instance. """ # Make sure the request is via HTTPS. We can only tell that by # looking for the header NGinx sets for us when it processes HTTPS # requests. if not self.development: secureHeader = request.getHeader('X-Forwarded-Protocol') if secureHeader != 'https': raise LoginFailed('OAuth2 requests must use https.') if response: try: credentials = binascii.a2b_base64(response + '===') except binascii.Error: raise LoginFailed('Non-base64 credentials.') try: username, password = credentials.split(':', 1) except ValueError: raise LoginFailed('Credentials could not be split on colon.') try: username = username.decode('utf-8') password = password.decode('utf-8') except UnicodeDecodeError: raise LoginFailed('Invalid UTF-8 in credentials.') else: username = u'anon' password = None token = request.getAllHeaders().get('x-fluiddb-access-token') if not token: raise LoginFailed('X-FluidDB-Access-Token header missing/empty.') return OAuth2Credentials(username, password, token)
def __exit__(self, exc_type, exc_value, traceback): if self.context: try: kerberos.authGSSServerClean(self.context) except kerberos.KrbError as e: msg = repr(e) log.msg(msg) raise LoginFailed(msg) if exc_value: msg = repr(exc_value) log.msg(msg) raise LoginFailed(msg)
def _makeLdapBase(self, ldapFolders, userName, propertyName): try: ldapBases = [] for folder in ldapFolders.split(','): folder = folder.strip() if not folder: continue parts = [] for part in folder.split('/'): part = part.strip() if not part: continue parts.append('%s=%s' % (propertyName, part)) ldapBases.append(','.join(reversed(parts))) return ldapBases except Exception as e: logger.error( "Login failed for %s, failed to parse LDAP %s Folders setting", propertyName, userName) logger.exception(e) raise LoginFailed( "An internal error occurred, ask admin to check Attune logs")
def authenticate(self, req: Request) -> Deferred: # To avoid cross-site request forgery, we must authenticate every API # call and not use session cookies. Since API calls are not made using # web browsers, most likely the client is not using session cookies # anyway. # http://en.wikipedia.org/wiki/Cross-site_request_forgery try: credentials = req.getCredentials() except UnicodeDecodeError as ex: return fail(LoginFailed(ex)) if credentials.name: return ensureDeferred(authenticateUser(self.userDB, credentials)) elif self.project.anonguest: return succeed(AnonGuestUser()) else: return fail(LoginFailed())
def authenticate(self, req: Request) -> Deferred: user = req.loggedInUser() if user is not None: # User has already authenticated. return succeed(user) elif self.project.anonguest: return succeed(AnonGuestUser()) else: # User must log in. return fail(LoginFailed())
def decode(self, response, request): """ Parse the Authorization header to retrieve the OAuth components. @param response: The value of the HTTP Authorization header. @param request: The incoming HTTP request. @return: an L{OAuthCredentials}. @raise L{LoginFailed}: if the format of the Authorization header is not valid. """ creds = response.split(',') # There are seven required OAuth headers, only version is optional if len(creds) >= 7: parameters = {} for cred in creds: parameter, value = cred.split('=') parameter = parameter.strip(' \r\n"') value = value.strip(' \r\n"') key = OAuthCredentials.oauthMapping.get(parameter) if key is None: raise LoginFailed('Invalid credentials') parameters[key] = unquote(value) # Apart from the OAuth header, we also need the URL and the method # of the incoming request parameters['url'] = request.uri parameters['method'] = request.method parameters['headers'] = request.getAllHeaders() parameters['version'] = parameters.get('version') arguments = {} for key, value in request.args.iteritems(): arguments[key.lower()] = value[-1] parameters['arguments'] = arguments # Check that all parameters are present, otherwise raise an # exception if len(parameters) == 12: return OAuthCredentials(**parameters) raise LoginFailed('Invalid credentials')
def checkPassBlocking(self, dbSession, userName, password, forService: int) -> List[str]: results = dbSession.query(InternalUserPassword) \ .join(InternalUserTuple) \ .filter(InternalUserTuple.userName == userName) \ .all() if not results or not results[0].password: raise UserPasswordNotSetException(userName) passObj = results[0] if passObj.password != PasswordUpdateController.hashPass(password): raise LoginFailed("Username or password is incorrect") groups = dbSession.query(InternalGroupTuple) \ .join(InternalUserGroupTuple) \ .filter(InternalUserGroupTuple.userId == passObj.userId) \ .all() groupNames = [g.groupName for g in groups] if forService == self.FOR_ADMIN: adminGroup = globalSetting(dbSession, ADMIN_LOGIN_GROUP) if adminGroup not in set(groupNames): raise LoginFailed("User is not apart of an authorised group") elif forService == self.FOR_OFFICE: officeGroup = globalSetting(dbSession, OFFICE_LOGIN_GROUP) if officeGroup not in set(groupNames): raise LoginFailed("User is not apart of an authorised group") elif forService == self.FOR_FIELD: fieldGroup = globalSetting(dbSession, MOBILE_LOGIN_GROUP) if fieldGroup not in set(groupNames): raise LoginFailed("User is not apart of an authorised group") else: raise Exception("InternalAuth:Unhandled forService type %s" % forService) return groupNames
def checkPassBlocking(self, dbSession, userName, password, forService: int) -> List[str]: """ Login User :param forService: :param dbSession: :param userName: The username of the user. :param password: The users secret password. :rtype """ assert forService in (1, 2, 3), "Unhandled for service type" ldapSettings: List[LdapSetting] = dbSession.query(LdapSetting) \ .all() if not ldapSettings: raise Exception("No LDAP servers configured.") firstException = None for ldapSetting in ldapSettings: if forService == self.FOR_ADMIN: if not ldapSetting.adminEnabled: continue elif forService == self.FOR_OFFICE: if not ldapSetting.desktopEnabled: continue elif forService == self.FOR_FIELD: if not ldapSetting.mobileEnabled: continue else: raise Exception("InternalAuth:Unhandled forService type %s" % forService) try: return self._tryLdap(dbSession, userName, password, ldapSetting) except LoginFailed as e: if not firstException: firstException = e logger.error("Login failed for %s, %s", userName, str(firstException)) if firstException: raise firstException raise LoginFailed("No LDAP providers found for this service")
def authenticate(self, req: Request) -> Deferred: try: credentials = req.getCredentials() except UnicodeDecodeError as ex: return fail(LoginFailed(ex)) tokenId = credentials.name if tokenId: try: token = authenticateToken(self.tokenDB, credentials) except KeyError: return fail(LoginFailed(f'Token {tokenId} does not exist')) if token.role is not self.__role: return fail( Unauthorized( f'Token {tokenId} is of the wrong type for this operation' )) if token.expired: return fail(Unauthorized(f'Token {tokenId} has expired')) return succeed(TokenUser(token)) elif self.project.anonguest: return succeed(AnonGuestUser()) else: return fail(LoginFailed())
def decode(self, challenge, request): with ServerGSSContext(self.serviceType) as context: res = context.step(challenge) if res < 0: raise LoginFailed() response = context.response() request.responseHeaders.addRawHeader( 'www-authenticate', '%s %s' % (self.scheme, response) ) if res == kerberos.AUTH_GSS_COMPLETE: principal = context.userName() return NegotiateCredentials(principal) raise LoginError()
def _checkPassBlocking(self, ormSession, userName, password, isFieldService: bool) -> List[str]: if not password: raise LoginFailed("Password is empty") # TODO Make the client tell us if it's for office or field lastException = None forService = InternalAuth.FOR_OFFICE if isFieldService: forService = InternalAuth.FOR_FIELD # TRY INTERNAL IF ITS ENABLED try: if forService == InternalAuth.FOR_FIELD \ and globalSetting(ormSession, INTERNAL_AUTH_ENABLED_FOR_FIELD): return InternalAuth().checkPassBlocking( ormSession, userName, password, forService) if forService == InternalAuth.FOR_OFFICE \ and globalSetting(ormSession, INTERNAL_AUTH_ENABLED_FOR_OFFICE): return InternalAuth().checkPassBlocking( ormSession, userName, password, forService) except Exception as e: lastException = e # TRY LDAP IF ITS ENABLED try: if globalSetting(ormSession, LDAP_AUTH_ENABLED): return LdapAuth().checkPassBlocking(ormSession, userName, password, forService) except Exception as e: lastException = e if lastException: raise lastException raise Exception( "No authentication handlers are enabled, enable one in settings")
def _tryLdap(self, dbSession, userName, password, ldapSetting: LdapSetting) -> List[str]: try: conn = ldap.initialize(ldapSetting.ldapUri) conn.protocol_version = 3 conn.set_option(ldap.OPT_REFERRALS, 0) # make the connection conn.simple_bind_s('%s@%s' % (userName, ldapSetting.ldapDomain), password) ldapFilter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=%s))" % userName dcParts = ','.join(['DC=%s' % part for part in ldapSetting.ldapDomain.split('.')]) ldapBases = [] if ldapSetting.ldapOUFolders: ldapBases += self._makeLdapBase(ldapSetting.ldapOUFolders, userName, "OU") if ldapSetting.ldapCNFolders: ldapBases += self._makeLdapBase(ldapSetting.ldapCNFolders, userName, "CN") if not ldapBases: raise LoginFailed("LDAP OU and/or CN search paths must be set.") userDetails = None for ldapBase in ldapBases: ldapBase = "%s,%s" % (ldapBase, dcParts) try: # Example Base : 'CN=atuser1,CN=Users,DC=synad,DC=synerty,DC=com' userDetails = conn.search_st(ldapBase, ldap.SCOPE_SUBTREE, ldapFilter, None, 0, 10) if userDetails: break except ldap.NO_SUCH_OBJECT: logger.warning("CN or OU doesn't exist : %s", ldapBase) except ldap.NO_SUCH_OBJECT: raise LoginFailed( "An internal error occurred, ask admin to check Attune logs") except ldap.INVALID_CREDENTIALS: raise LoginFailed("Username or password is incorrect") if not userDetails: raise LoginFailed("User doesn't belong to the correct CN/OUs") userDetails = userDetails[0][1] groups = [] for memberOf in userDetails['memberOf']: group = memberOf.decode().split(',')[0] if '=' in group: group = group.split('=')[1] groups.append(group) userTitle = None if userDetails['displayName']: userTitle = userDetails['displayName'][0].decode() email = None if userDetails['userPrincipalName']: email = userDetails['userPrincipalName'][0].decode() userUuid = None if userDetails['distinguishedName']: userUuid = userDetails['distinguishedName'][0].decode() if ldapSetting.ldapGroups: ldapGroups = set([s.strip() for s in ldapSetting.ldapGroups.split(',')]) if not ldapGroups & set(groups): raise LoginFailed("User is not apart of an authorised group") self._makeOrCreateInternalUserBlocking(dbSession, userName, userTitle, userUuid, email, ldapSetting.ldapTitle) return groups