Server overwrites External Identity Provider during REST call, fails at browser authentication

Hey SonarQube,
thanks for your awesome work. Here at Cloudogu we love and use your product since quite a time!

I maintain a plug-in that authenticates users against the single sign-on authentication solution CAS. Using the CAS identity provider for sign-on via browser worked just fine on SQ 7.9, though.

The authentication behaviour breaks after migration from SQ LTS 7.9 to LTS 8.9 when trying to authenticate REST requests. I dug deep into my and your code to see why this happens. After providing the required bug ticket infos I’d like to present data why I believe that ignores the CAS identity provider in favour of a static one. Please do not hesitate to ask your questions of which I am more than happy to answer.

versions used (SonarQube, Scanner, Plugin, and any relevant extension)

error observed (wrap logs/code around triple quote ``` for proper formatting)

After authenticating via REST, authentication with the same user and via browser fails:

May 17 10:18:59 ces docker/sonar[1577]: 2021.05.17 10:18:59 DEBUG web[AXl5XHUbwwQ4OFaKAAAF][o.j.c.c.v.Cas30ServiceTicketValidator] Server response: \n\n\n\n\n<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n    <cas:authenticationSuccess>\n        <cas:user>prefect</cas:user>\n        \n        \n\n        \n            <cas:attributes>\n                \n                   #011\n                   #011\n                    \n                        \n                        \n                            <cas:mail>prefect@galaxy.org</cas:mail>\n                        \n                        \n                        \n                    \n                \n                   #011\n                   #011\n                    \n                        \n                        \n                            <cas:surname>Prefect</cas:surname>\n                        \n                        \n                        \n                    \n                \n                   #011\n                   #011\n                    \n                        \n                        \n                            <cas:displayName>Ford Prefect</cas:displayName>\n                        \n                        \n                        \n                    \n                \n                   #011\n                   #011\n                    \n                        \n                        \n                            <cas:givenName>Ford</cas:givenName>\n                        \n                        \n                        \n                    \n                \n                   #011\n                   #011\n                    \n                        \n                        \n                        \n                        \n                            \n                        \n                    \n                \n                   #011\n                   #011\n                    \n                        \n                        \n                            <cas:cn>Ford Prefect</cas:cn>\n                        \n                        \n                        \n                    \n                \n                   #011\n                   #011\n                    \n                        \n                        \n                            <cas:username>prefect</cas:username>\n                        \n                        \n                        \n                    \n                \n            </cas:attributes>\n        \n\n    </cas:authenticationSuccess>\n</cas:serviceResponse>\n
May 17 10:18:59 ces docker/sonar[1577]: 2021.05.17 10:18:59 DEBUG web[AXl5XHUbwwQ4OFaKAAAF][o.s.p.c.LoginHandler] Received assertion. Authenticating with user Ford Prefect
May 17 10:18:59 ces docker/sonar[1577]: 2021.05.17 10:18:59 TRACE web[AXl5XHUbwwQ4OFaKAAAF][sql] time=1ms | sql=SELECT u.uuid as uuid, u.login as login, u.name as name, u.email as email, u.active as "active", u.scm_accounts as "scmAccounts", u.salt as "salt", u.crypted_password as "cryptedPassword", u.hash_method as "hashMethod", u.external_id as "externalId", u.external_login as "externalLogin", u.external_identity_provider as "externalIdentityProvider", u.user_local as "local", u.is_root as "root", u.onboarded as "onboarded", u.reset_password as "resetPassword", u.homepage_type as "homepageType", u.homepage_parameter as "homepageParameter", u.last_connection_date as "lastConnectionDate", u.last_sonarlint_connection as "lastSonarlintConnectionDate", u.created_at as "createdAt", u.updated_at as "updatedAt" FROM users u WHERE u.external_id=? AND u.external_identity_provider=? | params=prefect, cas
May 17 10:18:59 ces docker/sonar[1577]: 2021.05.17 10:18:59 TRACE web[AXl5XHUbwwQ4OFaKAAAF][sql] time=1ms | sql=SELECT u.uuid as uuid, u.login as login, u.name as name, u.email as email, u.active as "active", u.scm_accounts as "scmAccounts", u.salt as "salt", u.crypted_password as "cryptedPassword", u.hash_method as "hashMethod", u.external_id as "externalId", u.external_login as "externalLogin", u.external_identity_provider as "externalIdentityProvider", u.user_local as "local", u.is_root as "root", u.onboarded as "onboarded", u.reset_password as "resetPassword", u.homepage_type as "homepageType", u.homepage_parameter as "homepageParameter", u.last_connection_date as "lastConnectionDate", u.last_sonarlint_connection as "lastSonarlintConnectionDate", u.created_at as "createdAt", u.updated_at as "updatedAt" FROM users u WHERE u.external_login=? AND u.external_identity_provider=? | params=prefect, cas
May 17 10:18:59 ces docker/sonar[1577]: 2021.05.17 10:18:59 TRACE web[AXl5XHUbwwQ4OFaKAAAF][sql] time=1ms | sql=SELECT u.uuid as uuid, u.login as login, u.name as name, u.email as email, u.active as "active", u.scm_accounts as "scmAccounts", u.salt as "salt", u.crypted_password as "cryptedPassword", u.hash_method as "hashMethod", u.external_id as "externalId", u.external_login as "externalLogin", u.external_identity_provider as "externalIdentityProvider", u.user_local as "local", u.is_root as "root", u.onboarded as "onboarded", u.reset_password as "resetPassword", u.homepage_type as "homepageType", u.homepage_parameter as "homepageParameter", u.last_connection_date as "lastConnectionDate", u.last_sonarlint_connection as "lastSonarlintConnectionDate", u.created_at as "createdAt", u.updated_at as "updatedAt" FROM users u WHERE lower(u.email)=? AND u.active=true | params=prefect@galaxy.org
May 17 10:18:59 ces docker/sonar[1577]: 2021.05.17 10:18:59 ERROR web[AXl5XHUbwwQ4OFaKAAAF][o.s.p.c.CasIdentityProvider] authentication or logout failed
May 17 10:18:59 ces docker/sonar[1577]: org.sonar.server.authentication.event.AuthenticationException: Email 'prefect@galaxy.org' is already used
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.event.AuthenticationException$Builder.build(AuthenticationException.java:103)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.UserRegistrarImpl.generateExistingEmailError(UserRegistrarImpl.java:242)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.UserRegistrarImpl.detectEmailUpdate(UserRegistrarImpl.java:148)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.UserRegistrarImpl.registerNewUser(UserRegistrarImpl.java:97)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.UserRegistrarImpl.register(UserRegistrarImpl.java:76)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.BaseContextFactory$ContextImpl.authenticate(BaseContextFactory.java:82)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.plugins.cas.LoginHandler.handleLogin(LoginHandler.java:67)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.plugins.cas.CasIdentityProvider.init(CasIdentityProvider.java:69)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.InitFilter.handleBaseIdentityProvider(InitFilter.java:102)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.InitFilter.handleProvider(InitFilter.java:79)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.InitFilter.doFilter(InitFilter.java:72)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.platform.web.MasterServletFilter$GodFilterChain.doFilter(MasterServletFilter.java:139)
May 17 10:18:59 ces docker/sonar[1577]: #011at org.sonar.server.authentication.DefaultAdminCredentialsVerifierFilter.doFilter(DefaultAdminCredentialsVerifierFilter.java:89)
...

steps to reproduce

There are two similar scenarios (either first REST then UI auth, or the other way around) but I concentrate on the one which produces more stacktraces:

  1. given a user “Ford Prefect” exists that has never authenticated against CAS and SonarQube
  2. given the Sonar-CAS-plugin is installed and loaded into SonarQube
  3. when user “Ford Prefect” requests REST API https://192.168.x.y/sonar/api/plugins/installed with basic auth credentials
  4. then the Sonar-CAS-Plugin successfully authenticates the user against the CAS identity provider server → success
  5. then the user “Ford Prefect” is associated by SonarQube with the CAS identity provider → fail (see DB statement)
  6. when CAS-authenticated user “Ford Prefect” opens URL https://192.168.x.y/sonar/ via browser
  7. then the Sonar-CAS-Plugin successfully authenticates the user against the CAS identity provider server → success
  8. then SonarQube page opens → fail (see Stacktrace above)

potential workaround

I am fresh out of workaround ideas because SQ’s server authentication does not allow for hooking into the internal authentication workflow any further.

Further information

Because users of the sonar-cas-plugin use SonarQube since years I needed to find a way to fix the failure and so dived deep into your code. Looking into the database it shows that the user data are mistakenly associated with the external identity provider sonarqube instead of cas when authenticated via REST. If the first-time sign-on was done via the browser the external identity provider is correctly marked as cas but will fail at authenticating via REST. This snippet shows the wrongly marked user entry from SQ’s database that was created via REST:

select * from users;
         uuid         |    login     |   name   |        email         | crypted_password | salt | hash_method | active | scm_accounts | external_login | external_identity_provider | external_id | is_root | user_local | onboarded | homepage_type | homepage_parameter | last_connection_date |  created_at   |  updated_at   | reset_password | last_sonarlint_connection
----------------------+--------------+----------+----------------------+------------------+------+-------------+--------+--------------+----------------+----------------------------+-------------+---------+------------+-----------+---------------+--------------------+----------------------+---------------+---------------+----------------+---------------------------
AXl5Zryjp68wEhTe4268 | prefect          | Ford Prefect | prefect@galaxy.org     |                  |      |             | t      |              | prefect        | sonarqube                  | prefect     | f       | f          | f         |               |                    |        1621239447006 | 1621239446691 | 1621239447006 | f              |

Since SonarQube queries for user prefect and external identity provider cas this entry will never found so that SonarQube tries to create a new user (which fails).

About Sonar-CAS-Plugin’s implementation

The sonar-cas-plugin delivers SonarQube-CAS-Authentication since SonarQube 5.x and was rebuilt again and again to match newer SonarQube versions. Currently SonarQube 7.9 is the latest supported version.