Skip to content

Commit fdb0f0e

Browse files
authored
Merge pull request #473 from Breeding-Insight/feature/BI-2540
BI-2540 - Update bi_user Table for Alternate OAuth Provider(s)
2 parents 5ee551c + f5457c3 commit fdb0f0e

File tree

48 files changed

+286
-94
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+286
-94
lines changed

.env.template

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
USER_ID=<user id of system user>
44
GROUP_ID=<group id of system user>
55

6+
# GitHub OAuth variables. Only required if using GitHub as an OAuth provider.
7+
GITHUB_OAUTH_CLIENT_ID=<Client ID of GitHub OAuth app.>
8+
GITHUB_OAUTH_CLIENT_SECRET=<Client Secret of GitHub Oauth app.>
9+
610
ORCID_SANDBOX_AUTHENTICATION=<true or false; true=>use the Sandbox Orcid, false=>use the Production Orcid. Defaults to false.>
711

812
# Authentication variables
@@ -74,4 +78,4 @@ STUDY_START_DELAY=10s
7478
TRIAL_START_DELAY=15s
7579
TRAIT_START_DELAY=20s
7680
OBSERVATION_START_DELAY=25s
77-
OBSERVATION_UNIT_START_DELAY=30s
81+
OBSERVATION_UNIT_START_DELAY=30s

.github/workflows/build.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,7 @@ jobs:
7373
JWT_SECRET: ${{ secrets.JWT_SECRET }}
7474
OAUTH_CLIENT_ID: 123abc
7575
OAUTH_CLIENT_SECRET: asdfljkhalkbaldsfjasdfi238497098asdf
76+
GITHUB_OAUTH_CLIENT_ID: 12345678901234567890
77+
GITHUB_OAUTH_CLIENT_SECRET: 1234567890123456789012345678901234567890
7678
BRAPI_REFERENCE_SOURCE: breedinginsight.org
77-
BRAPI_DOCKER_IMAGE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.brapi_server_image || 'breedinginsight/brapi-java-server:develop' }}
79+
BRAPI_DOCKER_IMAGE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.brapi_server_image || 'breedinginsight/brapi-java-server:develop' }}

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ services:
4040
- API_INTERNAL_PORT=${API_INTERNAL_PORT}
4141
- API_INTERNAL_TEST_PORT=${API_INTERNAL_TEST_PORT}
4242
- OAUTH_CLIENT_ID=${OAUTH_CLIENT_ID}
43+
- GITHUB_OAUTH_CLIENT_ID=${GITHUB_OAUTH_CLIENT_ID}
44+
- GITHUB_OAUTH_CLIENT_SECRET=${GITHUB_OAUTH_CLIENT_SECRET}
4345
- JWT_DOMAIN=${JWT_DOMAIN}
4446
- DB_SERVER=${DB_SERVER}
4547
- DB_NAME=${DB_NAME}

pom.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@
192192
<artifactId>micronaut-inject</artifactId>
193193
<scope>compile</scope>
194194
</dependency>
195+
<dependency>
196+
<groupId>io.micronaut</groupId>
197+
<artifactId>micronaut-http-client</artifactId>
198+
<scope>compile</scope>
199+
</dependency>
195200
<dependency>
196201
<groupId>io.micronaut</groupId>
197202
<artifactId>micronaut-validation</artifactId>
@@ -619,6 +624,10 @@
619624
<url>jdbc:postgresql://${DB_SERVER}/${DB_NAME}</url>
620625
<user>${DB_USER}</user>
621626
<password>${DB_PASSWORD}</password>
627+
<locations>
628+
<location>filesystem:src/main/java/org/breedinginsight/db/migration</location>
629+
<location>filesystem:src/main/resources/db/migration</location>
630+
</locations>
622631
</configuration>
623632
<dependencies>
624633
<dependency>

src/main/java/org/breedinginsight/api/auth/AuthServiceLoginHandler.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import io.micronaut.security.token.jwt.cookie.JwtCookieLoginHandler;
3131
import io.micronaut.security.token.jwt.generator.AccessRefreshTokenGenerator;
3232
import io.micronaut.security.token.jwt.generator.AccessTokenConfiguration;
33-
import io.micronaut.security.token.jwt.generator.JwtGeneratorConfiguration;
3433
import lombok.extern.slf4j.Slf4j;
3534
import org.breedinginsight.api.model.v1.auth.SignUpJWT;
3635
import org.breedinginsight.model.ProgramUser;
@@ -83,7 +82,7 @@ public AuthServiceLoginHandler(JwtCookieConfiguration jwtCookieConfiguration,
8382

8483
@Override
8584
public MutableHttpResponse<?> loginSuccess(UserDetails userDetails, HttpRequest<?> request) {
86-
// Called when login to orcid is successful.
85+
// Called when login to OAuth provider is successful.
8786
// Check if our login to our system is successful.
8887
if (request.getCookies().contains(accountTokenCookieName)) {
8988
Cookie accountTokenCookie = request.getCookies().get(accountTokenCookieName);
@@ -124,7 +123,7 @@ public MutableHttpResponse<?> loginSuccess(UserDetails userDetails, HttpRequest<
124123

125124
private AuthenticatedUser getUserCredentials(UserDetails userDetails) throws AuthenticationException {
126125

127-
Optional<User> user = userService.getByOrcid(userDetails.getUsername());
126+
Optional<User> user = userService.getByOAuthId(userDetails.getUsername());
128127

129128
if (user.isPresent()) {
130129
if (user.get().getActive()) {
@@ -159,9 +158,20 @@ public MutableHttpResponse<?> loginFailed(AuthenticationResponse authenticationF
159158
}
160159
}
161160

161+
private String parseOAuthProvider(HttpRequest request) {
162+
// The request path will be something like "/sso/success/github".
163+
if (request.getPath().toLowerCase().contains("github")) {
164+
return "github";
165+
} else {
166+
// Default to ORCID.
167+
return "orcid";
168+
}
169+
}
170+
162171
private MutableHttpResponse newAccountCreationResponse(UserDetails userDetails, String accountToken, HttpRequest request) {
163172

164-
String orcid = userDetails.getUsername();
173+
String oAuthId = userDetails.getUsername();
174+
String oAuthProvider = parseOAuthProvider(request);
165175
SignUpJWT signUpJWT;
166176
try {
167177
signUpJWT = signUpJwtService.validateAndParseAccountSignUpJwt(accountToken);
@@ -185,9 +195,9 @@ private MutableHttpResponse newAccountCreationResponse(UserDetails userDetails,
185195
}
186196

187197
if (newUser.getAccountToken().equals(signUpJWT.getJwtId().toString())) {
188-
// Assign orcid to that user
198+
// Assign OAuth Id and provider to that user.
189199
try {
190-
userService.updateOrcid(newUser.getId(), orcid);
200+
userService.updateOAuthInfo(newUser.getId(), oAuthId, oAuthProvider);
191201
} catch (DoesNotExistException e) {
192202
MutableHttpResponse resp = HttpResponse.seeOther(URI.create(newAccountErrorUrl));
193203
return resp;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* See the NOTICE file distributed with this work for additional information
3+
* regarding copyright ownership.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.breedinginsight.api.auth;
19+
20+
import io.micronaut.http.annotation.Get;
21+
import io.micronaut.http.annotation.Header;
22+
import io.micronaut.http.client.annotation.Client;
23+
import io.reactivex.Flowable;
24+
25+
@Header(name = "User-Agent", value = "Micronaut")
26+
@Client("https://api.github.com")
27+
public interface GithubApiClient {
28+
29+
@Get("/user")
30+
Flowable<GithubUser> getUser(@Header("Authorization") String authorization);
31+
}
32+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* See the NOTICE file distributed with this work for additional information
3+
* regarding copyright ownership.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.breedinginsight.api.auth;
19+
20+
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
21+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
22+
import io.micronaut.core.annotation.Introspected;
23+
import lombok.Getter;
24+
25+
@Introspected
26+
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
27+
@Getter
28+
public class GithubUser {
29+
30+
private String id;
31+
// The login will be the unique GitHub username.
32+
private String login;
33+
private String name;
34+
private String email;
35+
36+
}
37+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* See the NOTICE file distributed with this work for additional information
3+
* regarding copyright ownership.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.breedinginsight.api.auth;
19+
20+
import io.micronaut.core.annotation.Nullable;
21+
import io.micronaut.core.async.publisher.Publishers;
22+
import io.micronaut.security.authentication.AuthenticationResponse;
23+
import io.micronaut.security.authentication.UserDetails;
24+
import io.micronaut.security.oauth2.endpoint.authorization.state.State;
25+
import io.micronaut.security.oauth2.endpoint.token.response.OauthUserDetailsMapper;
26+
import io.micronaut.security.oauth2.endpoint.token.response.TokenResponse;
27+
import lombok.extern.slf4j.Slf4j;
28+
import org.reactivestreams.Publisher;
29+
30+
31+
import javax.inject.Named;
32+
import javax.inject.Singleton;
33+
import java.util.Collections;
34+
import java.util.List;
35+
36+
@Slf4j
37+
@Named("github")
38+
@Singleton
39+
class GithubUserDetailsMapper implements OauthUserDetailsMapper {
40+
41+
private final GithubApiClient apiClient;
42+
43+
GithubUserDetailsMapper(GithubApiClient apiClient) {
44+
this.apiClient = apiClient;
45+
}
46+
47+
@Override
48+
public Publisher<UserDetails> createUserDetails(TokenResponse tokenResponse) {
49+
return Publishers.just(new UnsupportedOperationException());
50+
}
51+
52+
@Override
53+
public Publisher<AuthenticationResponse> createAuthenticationResponse(TokenResponse tokenResponse, @Nullable State state) {
54+
return apiClient.getUser("token " + tokenResponse.getAccessToken())
55+
.map(user -> {
56+
List<String> roles = Collections.singletonList("ROLE_GITHUB");
57+
return new UserDetails(user.getLogin(), roles);
58+
});
59+
}
60+
}
61+

src/main/java/org/breedinginsight/daos/ProgramUserDAO.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,14 @@ public List<ProgramUser> getProgramUsersByUserId(UUID userId) {
120120
return parseRecords(records, createdByUser, updatedByUser);
121121
}
122122

123-
public List<ProgramUser> getProgramUsersByOrcid(String orcid) {
123+
public List<ProgramUser> getProgramUsersByOAuthId(String oAuthId) {
124124

125125
BiUserTable createdByUser = BI_USER.as("createdByUser");
126126
BiUserTable updatedByUser = BI_USER.as("updatedByUser");
127127

128128
// TODO: When we allow for pulling archived users, active condition won't be hardcoded.
129129
Result<Record> records = getProgramUsersQuery(createdByUser, updatedByUser)
130-
.where(BI_USER.ORCID.eq(orcid))
130+
.where(BI_USER.OAUTH_ID.eq(oAuthId))
131131
.and(PROGRAM.ACTIVE.eq(true))
132132
.fetch();
133133

src/main/java/org/breedinginsight/daos/UserDAO.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ public interface UserDAO extends DAO<BiUserRecord, BiUserEntity, UUID> {
3232

3333
Optional<User> getUser(UUID id);
3434

35-
Optional<User> getUserByOrcId(String orcid);
35+
Optional<User> getUserByOAuthId(String oAuthId);
3636

3737
BiUserEntity fetchOneById(UUID value);
3838

3939
List<BiUserEntity> fetchByEmail(String... values);
4040

41-
List<BiUserEntity> fetchByOrcid(String... values);
41+
List<BiUserEntity> fetchByOauthId(String... values);
4242
}

0 commit comments

Comments
 (0)