nagaboinaramgopal opened a new pull request, #14205:
URL: https://github.com/apache/cloudstack/pull/14205
### Description
The OAuth2 plugin resolves a login to a `UserOAuth2Authenticator` through a
fixed
provider-name to Spring-bean map. Each OIDC vendor is its own bean
(`GoogleOAuth2Provider`, `GithubOAuth2Provider`, `KeycloakOAuth2Provider`)
running the
same authorization-code flow with no vendor-specific logic in it. Two
consequences:
adding an IdP means shipping a new class, and because `provider` is
simultaneously the
display name and the routing key, a domain can register exactly one
`keycloak`.
This PR decouples the two by adding a `type` column to `oauth_provider`:
* `OAuth2AuthManagerImpl.getUserOAuth2AuthenticationProvider` looks the name
up in the
bean map first, as before, and only on a miss falls back to the
registration's `type`.
`provider` becomes an admin-chosen label; `type` selects the
implementation. One bean
can serve any number of registrations under arbitrary names.
* `getUserOAuth2AuthenticationProvider`, `verifyUser` and
`verifySecretCodeAndFetchEmail`
now carry the registration name, so a shared bean knows which row it is
acting for.
* Existing `google`/`github`/`keycloak` rows have a null `type` and continue
to dispatch
by name, on the same code path as today.
`GenericOIDCOAuth2Provider` is registered under `type=oidc` and configured
with the issuer
URL alone. It reads `token_endpoint` and `jwks_uri` from the issuer's
`.well-known/openid-configuration` (cached; the document's `issuer` must
match the
configured value), and validates the `id_token` before trusting any claim in
it: signature
against the JWKS key named by the token `kid`, then issuer, audience and
expiry, via CXF's
`JwsJwtCompactConsumer` / `JwkUtils` / `JwtUtils`. That is
`cxf-rt-rs-security-jose`,
already on the plugin classpath, so no new dependency.
Unlike the vendor providers, it holds no token in an instance field. The
code is exchanged
per call, and a code verified through `verifyOAuthCodeAndGetUser` is cached
for 60s and
consumed once, so the `oauthlogin` that immediately follows does not
re-present it to the
IdP.
**API / response fix:** `ListOAuthProvidersCmd` and `UpdateOAuthProviderCmd`
derived the
response `enabled` flag from `authenticatorPluginNames.contains(provider)`, a
name-to-bean check, which reported every generically-named registration as
disabled. Both
now also accept a registration whose `type` resolves to a plugin.
`type` is settable on register but not on update: changing it would swap the
implementation under an existing row.
**Schema:** adds `type` and `issuer_url` to `oauth_provider` in the 4.23.0.0
to 24.0.0
upgrade file.
**UI:** `Login.vue` renders a button per registered `oidc` provider (in
addition to the
existing google/github/keycloak buttons), labelled "Sign in with <label>".
Clicking it
reads the issuer's `authorization_endpoint` from discovery and starts the
authorization-code flow. As with the keycloak provider, the registered
`redirecturi` must
carry `verifyOauth` (for example `https://<ui-host>/client?verifyOauth`) so
the callback
lands on the verify handler.
Fixes: #9609
### Types of changes
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Enhancement (improves an existing feature and functionality)
- [ ] Cleanup (Code refactoring and cleanup, that may add test cases)
- [ ] Build/CI
- [ ] Test (unit or integration test code)
### Feature/Enhancement Scale or Bug Severity
#### Feature/Enhancement Scale
- [x] Major
- [ ] Minor
### Screenshots (if appropriate):
### How Has This Been Tested?
**Unit tests** - oauth2 module, 103 tests green.
* `GenericOIDCOAuth2ProviderTest` (27 tests) generates an RSA keypair,
publishes the
matching JWKS, and asserts that a genuinely signed token is accepted while
these are
rejected: a token with altered claims, one signed by a different key, one
naming a `kid`
absent from the JWKS, and issuer / audience / expiry mismatches. Also
covers discovery
document validation (issuer mismatch) and that a verified code is redeemed
exactly once.
* `OAuth2AuthManagerImplTest` covers the `type` fallback, an unknown `type`
rejected at
register time, and a real bean name winning without a DB lookup.
* Vendor provider tests updated for the name-carrying signatures.
* `DatabaseUpgradeCheckerTest` green with the new schema file.
**Integration** - KVM advanced zone against Keycloak 25. Registered an
`oidc` provider
pointing at a Keycloak realm and logged a real user in through the Keycloak
form:
`verifyOAuthCodeAndGetUser` validates the RS256 signature against the realm
JWKS and
returns the email, `oauthlogin` with the same code issues the session, and
`listOauthProviders` reports the generically-named provider as enabled.
#### How did you try to break this feature and the system with this change?
* A login presenting a valid, correctly signed token but claiming a
different CloudStack
user's email - rejected, no session.
* Replay of an already-consumed code - rejected, no session.
* A bogus/garbage code - rejected, no session.
* After each of the above, a fresh correct login still succeeds.
* Existing `google`/`github`/`keycloak` registrations are untouched and
still log in
(name-based dispatch, null `type`).
* Discovery document whose `issuer` does not match the configured issuer URL
- rejected.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]