Details

      Description

      Debug log must be added to the Web Server log file (web.log) whenever a user authenticates to SonarQube, either successfully or not.

      These logs will include:

      • IP:
      • X-Forwarded-For Header (it's a nice to have to have split field of IP and X-Forwarded-For)
      • login
      • status (successful or failure)
      • reason of failure (best effort here, we should not hide the stack trace for Github in order to be able to investigate manually the failure)
      • provider (github, ldap, default, ...)

      log format

      As the log will include a user input (the login) we must take into account for the parsing that this field may contain any character. It is important that no matter these character we will be able to extract the login from the line (in order to detect a SQL injection or other such attack).

      Authentication mechanisms in SQ:

      : authentication success event
      : authentication failure event
      : logout (won't be covered in the ticket because we depend on SONAR-7774 as capturing the logout even hooking into Ruby code is far too complexe – if feasible at all...)

      Global filter

      applies to all URLs

      1. authenticate with SSO if SSO header is present (see below)
      2. otherwise, authenticate with JWT token (see below)
      3. otherwise, authenticate with basic authentication (see below)

      JWT token

      JWT token verification includes the verification of the CSRF cookie for applicable URLs (api URLs with verb PUT, POST and DELETE not handled by Rails)

      Authentication with JWT will fail if:

      1. if cookie is not present, skip
      2. read cookie and decode it
        • if cookie can't be decoded (it contains garbage or secret has changed), skip JWT authentication
        • if token is expired, skip JWT authentication, user is logged out
        • if data is missing in JWT token, fail
        • in case of any other error, fail
      3. check session isn't timed out
        • if so, skip JWT authentication, user is logged out
      4. check CSRF header
        • do not check unless POST/PUT/DELETE on /api/* not handled by Rails
        • if header value is missing or different from CSRF value in token, fail
      5. refresh JWT token and cookies
        • refresh expiration date of token
        • update JWT cookie with new token and update cookie's expiration
        • update CSRF cookie expiration (but value unchanged)
      6. look up user in SQ DB
        • if user for login doesn't exist or is inactive, skip JWT authentication

      When JWT token is invalid, the JWT token is not cleared by the server. The reason clearing the cookie can not be done has been forgotten (but Julien Lancelot has a clear memory that clearing the cookie caused failures, at lest under some circonstances)

      Form

      login by dedicated URL /api/authentication/login (implemented as a Filter)

      1. try local authentication
        1. retrieve user from DB by login, ignore if inactive or local
        2. check supplied password matches password hash in DB, fail authentication if it doesn't match
      2. if local authentication skipped, delegate to ExtensionPoint SecurityRealm (eg. LDAP) if present (see below)
      3. if SecurityRealm skipped, fail authentication
      4. otherwise, a JWT token is generated and cookies for JWT and CSRF are set (new CSRF value)

      Logout URL sessions/logout

      • rails code in sessions_controller.rb
      • deletes JWT and CSRF cookies

      WS api/authentication/validate

      allows to check a user/password pair or a user token and also supports JWT and CSRF cookies verification

      1. read JWT token and check CSRF cookie
        • failure if Token can't be read, secret has changed, token timed out, CSRF check failed
      2. check user/password against SQ DB and return "true" if SQ user is local
      3. otherwise, delegate to ExtensionPoint SecurityRealm (eg. LDAP) if present, via class RealmAuthenticator (see below)

      uses JwtHttpHandler and BasicAuthenticator so same code as "real" authentication mechanisms => authentication events to ignore?

      Basic

      login and password or user token as login without password in standard HTTP header

      1. try local authentication
        1. retrieve user from DB by login, ignore if inactive or local
        2. check supplied password matches password hash in DB, fail authentication if it doesn't match
      2. if local authentication skipped, try authentication with SecurityRealm (eg. LDAP) if present (see below)
      3. if SecurityRealm skipped too, fail authentication

      This does not generate a JWT token (ie. it does not create a "session")

      SecurityRealm

      API to delegate authentication via SonarQube (ie. either BASIC or FORM) to an external system. Main implementation is LDAP.

      implemented in class RealmAuthenticator.

      1. delegates to the one Realm
        1. retrieve user details from external system, if none retrieved fail
        2. authenticate against external system, if failure in external system, fail authentication
        3. optionally retrieve groups
      2. create or update user in DB (actual DB changes go though class UserIdentityAuthenticator, see below)

      This does not generate a JWT token (ie. it does not create a "session")

      SSO

      Single-sign-on authentication based on HTTP header defined in property "sonar.sso.loginHeader" (most commonly "X-Forwarded-Login").

      If header is not present in request, no SSO authentication occurs.

      This authentication directly handles JWT token.

      1. fail if header is empty
      2. if JWT token is present
        1. do JWT verification (see above)
        2. if SSO information must be refresh (defined by property "sonar.sso.refreshIntervalInMinutes", defaults to 5), ignore user from token
        3. unless login in token is different from login in SSO header, use user from token
      3. get login, name (defaults to login if not present, email and (optionally) groups from headers (configurable by properties "sonar.sso.nameHeader", "sonar.sso.emailHeader" and "sonar.sso.groupsHeader")
      4. create or update user in DB (actual DB changes go though class UserIdentityAuthenticator, see below)
      5. generate JWT token and CSRF cookie

      External Identity provider

      This API has two Extension points classes: OAuth2IdentityProvider and BaseIdentityProvider. It allows to delegate the whole authentication to an external system. Main implementation is OAuth based authentications such as GitHub.

      OAuth2

      From SQ's core point of view, OAuth2 authentication has two steps.

      Step1: init authentication workflow when GET on /sessions/init/[provider key] occurs

      • URL handled by a Filter (InitFilter) but the Global filter applies (see above)
      • method OAuth2IdentityProvider#init is responsible for handling the HTTP response (most likely with a redirect to the external provider)
      • OAuth2IdentityProvider#init may throw UnauthorizedException or any other unchecked exception
        • the former can specify the path the user must be redirected to but no log will be written by the core
        • the later will be redirected to the /sessions/unauthorized and a log will be written by the core

      Step 2: authenticate user on SonarQube when callback is called by external system (when POST or GET on /oauth2/callback/[provider key] occurs)

      • URL handled by a Filter (OAuth2CallbackFilter) but the Global filter applies (see above)
      • method OAuth2IdentityProvider#callback is responsible for:
        • creating or updating the SQ User in DB (actual DB changes go though class UserIdentityAuthenticator, see below) from data retrieved from the request
        • optionnally triger the check of CSRF
        • finally redirect to the page requested by the user before it was redirected to authentication
      • OAuth2IdentityProvider#callback may throw UnauthorizedException or any other unchecked exception
        • the former can specify the path the user must be redirected to but no log will be written by the core
        • the later will be redirected to the /sessions/unauthorized and a log will be written by the core

      UserIdentityAuthenticator)

      create or update user in DB

      1. fail if login, name and/or email is empty or too long
      2. update existing user if active
      3. otherwise create new user
        • fail if creating new user if disabled for current provider (cf. IdentityProvider#allowsUsersToSignUp())
        • fail if email already in use

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                sebastien.lesaint Sebastien Lesaint
                Reporter:
                sebastien.lesaint Sebastien Lesaint
              • Votes:
                0 Vote for this issue
                Watchers:
                4 Start watching this issue

                Dates

                • Due:
                  Created:
                  Updated:
                  Resolved: