-
Notifications
You must be signed in to change notification settings - Fork 501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
9229 - Add bearer token auth mechanism for OIDC user with feature flag #9591
Conversation
johannes-darms
commented
May 15, 2023
•
edited by pdurbin
Loading
edited by pdurbin
- closes As a developer, I'd like to use Bearer Token to send authenticated API requests #9229
- replaces 9229 - Add bearer token auth mechanism with feature flag #9532
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested the previous iteration of this pull request (#9532) and it worked so I'm approving this.
It looks like @johannes-darms incorporated all of my suggestions (thanks!).
@kcondon please note that the previous PR has all the details on how to test. I did my testing with Docker. I'm happy to walk your through what I did.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry to be pedantic, but I do see some places that IMHO could result in a bad API user experience. Thanks for keeping up the good work.
@@ -2391,6 +2404,9 @@ please find all known feature flags below. Any of these flags can be activated u | |||
* - api-session-auth | |||
- Enables API authentication via session cookie (JSESSIONID). **Caution: Enabling this feature flag exposes the installation to CSRF risks!** We expect this feature flag to be temporary (only used by frontend developers, see `#9063 <https://github.com/IQSS/dataverse/issues/9063>`_) and removed once support for bearer tokens has been implemented (see `#9229 <https://github.com/IQSS/dataverse/issues/9229>`_). | |||
- ``Off`` | |||
* - api-bearer-auth | |||
- Enables API authentication via Bearer Token for OIDC User Accounts. **Information: This feature works only for OIDC UserAccounts!** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Enables API authentication via Bearer Token for OIDC User Accounts. **Information: This feature works only for OIDC UserAccounts!** | |
- Enables API authentication via Bearer Token for OIDC User Accounts. **Note: This feature works only when using an :doc:`OpenID Connect authentication provider <oidc>`!** |
.. _bearer-token-auth: | ||
|
||
Bearer Token Authentication | ||
--------------------------- | ||
|
||
Bearer tokens are defined in `RFC 6750`_ and can be used as an alternative to API tokens. This is an experimental feature hidden behind a feature flag. | ||
|
||
.. _RFC 6750: https://tools.ietf.org/html/rfc6750 | ||
|
||
To enable bearer tokens, you must install and configure Keycloak (for now, see :ref:`oidc-dev` in the Developer Guide) and enable ``api-bearer-auth`` under :ref:`feature-flags`. | ||
|
||
You can test that bearer tokens are working by following the example under :ref:`bearer-tokens` in the API Guide. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do understand this is explained in the API docs (perfect place), this repetition and elaboration here seems somewhat misplaced IMHO. Shouldn't we keep this in the API guide?
@@ -12,6 +12,7 @@ services: | |||
- DATAVERSE_DB_HOST=postgres | |||
- DATAVERSE_DB_PASSWORD=secret | |||
- DATAVERSE_DB_USER=${DATAVERSE_DB_USER} | |||
- DATAVERSE_FEATURE_API_BEARER_AUTH=1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have this feature on by default. I'd rather keep this file less feature enabled, but more bare-bones.
dev_keycloak: | ||
container_name: "dev_keycloack" | ||
image: 'quay.io/keycloak/keycloak:19.0' | ||
hostname: keycloak | ||
environment: | ||
- KEYCLOAK_ADMIN=kcadmin | ||
- KEYCLOAK_ADMIN_PASSWORD=kcpassword | ||
- KEYCLOAK_LOGLEVEL=DEBUG | ||
- KC_HOSTNAME_STRICT=false | ||
networks: | ||
dataverse: | ||
aliases: | ||
- keycloak.mydomain.com #create a DNS alias within the network (add the same alias to your /etc/hosts to get a working OIDC flow) | ||
command: start-dev --import-realm --http-port=8090 # change port to 8090, so within the network and external the same port is used | ||
ports: | ||
- "8090:8090" | ||
volumes: | ||
- './conf/keycloak/oidc-realm.json:/opt/keycloak/data/import/oidc-realm.json' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please note:
- Keycloak 19 is outdated.
- I dislike adding more containers than necessary here. This is an experimental feature and right now, for most of development this is waste of CPU cycles. Can we make this
scale: $ENABLE_KEYCLOAK
, haveENABLE_KEYCLOAK=0
in.env
so you need to enable this by addingENABLE_KEYCLOAK=1
when starting this?
@Inject | ||
protected UserServiceBean userSvc; | ||
private static final Logger logger = Logger.getLogger(BearerTokenAuthMechanism.class.getCanonicalName()); | ||
@Override |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Override | |
@Override |
/** | ||
* Enables API authentication via Bearer Token. | ||
* @apiNote Raise flag by setting "dataverse.feature.api-bearer-auth" | ||
* @since Dataverse @TODO: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO. Maybe fine to put 5.14 here, I have a feeling this might make it.
sut.authSvc = Mockito.mock(AuthenticationServiceBean.class); | ||
sut.userSvc = Mockito.mock(UserServiceBean.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move these into setUp()
to make the tests more readable. (Goes as well for all the other occurences...)
} | ||
|
||
@Test | ||
@JvmSetting(key = JvmSettings.FEATURE_FLAG, value = "true", varArgs = "api-bearer-auth") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry that this is a bit messy - I did not yet implement setting something like this on a class level, this needs tuning. (Would avoid the repetition)
return null; | ||
} | ||
} | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not covered by a test, but is part of the API contract. Please make sure there is a unit test for this.
* @throws IOException | ||
* @throws OAuth2Exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When are these exceptions thrown?
src/main/java/edu/harvard/iq/dataverse/api/auth/BearerTokenAuthMechanism.java
Outdated
Show resolved
Hide resolved
@poikilotherm I'm not sure if this was your intention by selecting "request changes" but merging is blocked until you switch to "comment" or "approve". |
Co-authored-by: Oliver Bertuch <poikilotherm@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@poikilotherm and I chatted about this PR and left some comment.
src/main/java/edu/harvard/iq/dataverse/api/auth/BearerTokenAuthMechanism.java
Outdated
Show resolved
Hide resolved
return userInfo.get(); | ||
} | ||
} catch ( IOException| OAuth2Exception e) { | ||
logger.log(Level.FINE, "Bearer token detected, provider " + provider.getId() + " indicates an invalid Token, skipping", e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like the 401 is sent later because we aren't doing anything in the catch here.
* @throws IOException | ||
* @throws OAuth2Exception | ||
*/ | ||
public Optional<UserRecordIdentifier> getUserIdentifierForValidToken(BearerAccessToken accessToken) throws IOException, OAuth2Exception{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is to just return an empty optional rather than throwing an OAuth2Exception.
Tested with assistance from @pdurbin. Looks good from a basic functionality perspective. I know there are additional suggestions/to dos but seems ok to merge this step? |