From 742f5defd98e316d04d1ffed1a05567ee0c2466a Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:36:00 -0500 Subject: [PATCH] [BI-2539] - added GitHub OAuth support --- .env.template | 4 ++ pom.xml | 5 ++ .../api/auth/GithubApiClient.java | 32 ++++++++++ .../breedinginsight/api/auth/GithubUser.java | 37 +++++++++++ .../api/auth/GithubUserDetailsMapper.java | 61 +++++++++++++++++++ src/main/resources/application.yml | 11 ++++ 6 files changed, 150 insertions(+) create mode 100644 src/main/java/org/breedinginsight/api/auth/GithubApiClient.java create mode 100644 src/main/java/org/breedinginsight/api/auth/GithubUser.java create mode 100644 src/main/java/org/breedinginsight/api/auth/GithubUserDetailsMapper.java diff --git a/.env.template b/.env.template index 66a62fb5d..5d9e7b531 100644 --- a/.env.template +++ b/.env.template @@ -3,6 +3,10 @@ USER_ID= GROUP_ID= +# GitHub OAuth variables. Only required if using GitHub as an alternative to ORCID. +GITHUB_OAUTH_CLIENT_ID= +GITHUB_OAUTH_CLIENT_SECRET= + ORCID_SANDBOX_AUTHENTICATION=use the Sandbox Orcid, false=>use the Production Orcid. Defaults to false.> # Authentication variables diff --git a/pom.xml b/pom.xml index 4280fd0b1..28d93a5a8 100644 --- a/pom.xml +++ b/pom.xml @@ -190,6 +190,11 @@ micronaut-inject compile + + io.micronaut + micronaut-http-client + compile + io.micronaut micronaut-validation diff --git a/src/main/java/org/breedinginsight/api/auth/GithubApiClient.java b/src/main/java/org/breedinginsight/api/auth/GithubApiClient.java new file mode 100644 index 000000000..a25bb9f41 --- /dev/null +++ b/src/main/java/org/breedinginsight/api/auth/GithubApiClient.java @@ -0,0 +1,32 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.breedinginsight.api.auth; + +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Header; +import io.micronaut.http.client.annotation.Client; +import io.reactivex.Flowable; + +@Header(name = "User-Agent", value = "Micronaut") +@Client("https://api.github.com") +public interface GithubApiClient { + + @Get("/user") + Flowable getUser(@Header("Authorization") String authorization); +} + diff --git a/src/main/java/org/breedinginsight/api/auth/GithubUser.java b/src/main/java/org/breedinginsight/api/auth/GithubUser.java new file mode 100644 index 000000000..a71dc64fc --- /dev/null +++ b/src/main/java/org/breedinginsight/api/auth/GithubUser.java @@ -0,0 +1,37 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.breedinginsight.api.auth; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.micronaut.core.annotation.Introspected; +import lombok.Getter; + +@Introspected +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +@Getter +public class GithubUser { + + private String id; + // The login will be the unique GitHub username. + private String login; + private String name; + private String email; + +} + diff --git a/src/main/java/org/breedinginsight/api/auth/GithubUserDetailsMapper.java b/src/main/java/org/breedinginsight/api/auth/GithubUserDetailsMapper.java new file mode 100644 index 000000000..02515f766 --- /dev/null +++ b/src/main/java/org/breedinginsight/api/auth/GithubUserDetailsMapper.java @@ -0,0 +1,61 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.breedinginsight.api.auth; + +import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.async.publisher.Publishers; +import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.UserDetails; +import io.micronaut.security.oauth2.endpoint.authorization.state.State; +import io.micronaut.security.oauth2.endpoint.token.response.OauthUserDetailsMapper; +import io.micronaut.security.oauth2.endpoint.token.response.TokenResponse; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; + + +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.Collections; +import java.util.List; + +@Slf4j +@Named("github") +@Singleton +class GithubUserDetailsMapper implements OauthUserDetailsMapper { + + private final GithubApiClient apiClient; + + GithubUserDetailsMapper(GithubApiClient apiClient) { + this.apiClient = apiClient; + } + + @Override + public Publisher createUserDetails(TokenResponse tokenResponse) { + return Publishers.just(new UnsupportedOperationException()); + } + + @Override + public Publisher createAuthenticationResponse(TokenResponse tokenResponse, @Nullable State state) { + return apiClient.getUser("token " + tokenResponse.getAccessToken()) + .map(user -> { + List roles = Collections.singletonList("ROLE_GITHUB"); + return new UserDetails(user.getLogin(), roles); + }); + } +} + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 85987ef9d..3550a56b9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -59,6 +59,17 @@ micronaut: jwks-uri: ${OAUTH_OPENID_JWKSURI:`https://sandbox.orcid.org/oauth/jwks`} user-info: url: ${OAUTH_OPENID_USERINFOURL:`https://sandbox.orcid.org/oauth/userinfo`} + github: + client-id: ${GITHUB_OAUTH_CLIENT_ID} + client-secret: ${GITHUB_OAUTH_CLIENT_SECRET} + scopes: + - user:email + - read:user + authorization: + url: https://github.com/login/oauth/authorize + token: + url: https://github.com/login/oauth/access_token + auth-method: client-secret-basic state: cookie: cookie-max-age: 10m