Skip to content

Commit 0dfc7e8

Browse files
author
th37rose
committed
Implemented the generic Auth feature.
1 parent 2a096be commit 0dfc7e8

File tree

13 files changed

+314
-17
lines changed

13 files changed

+314
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.lowcoder.sdk.auth;
2+
3+
import lombok.Getter;
4+
import lombok.experimental.SuperBuilder;
5+
import lombok.extern.jackson.Jacksonized;
6+
7+
/**
8+
* This class is for Generic Auth Provider
9+
*/
10+
@Getter
11+
@SuperBuilder
12+
@Jacksonized
13+
public class Oauth2GenericAuthConfig extends Oauth2SimpleAuthConfig {
14+
private String issuerUri;
15+
private String authorizationEndpoint;
16+
private String tokenEndpoint;
17+
private String userInfoEndpoint;
18+
}

server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/Oauth2SimpleAuthConfig.java

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public String getAuthorizeUrl() {
3838
case AuthTypeConstants.GITHUB -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.GITHUB_AUTHORIZE_URL);
3939
case AuthTypeConstants.ORY -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.ORY_AUTHORIZE_URL);
4040
case AuthTypeConstants.KEYCLOAK -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.KEYCLOAK_AUTHORIZE_URL);
41+
case AuthTypeConstants.GENERIC -> replaceAuthUrlClientIdPlaceholder(((Oauth2GenericAuthConfig)this).getAuthorizationEndpoint());
4142
default -> null;
4243
};
4344
}

server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/constants/AuthTypeConstants.java

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ public class AuthTypeConstants {
1010
public static final String GITHUB = "GITHUB";
1111
public static final String ORY = "ORY";
1212
public static final String KEYCLOAK = "KEYCLOAK";
13+
public static final String GENERIC = "GENERIC";
1314
}

server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/util/JsonUtils.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@
1313
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
1414
import jakarta.annotation.Nullable;
1515
import lombok.extern.slf4j.Slf4j;
16-
import org.lowcoder.sdk.auth.EmailAuthConfig;
17-
import org.lowcoder.sdk.auth.Oauth2KeycloakAuthConfig;
18-
import org.lowcoder.sdk.auth.Oauth2OryAuthConfig;
19-
import org.lowcoder.sdk.auth.Oauth2SimpleAuthConfig;
16+
import org.lowcoder.sdk.auth.*;
2017

2118
import java.nio.charset.StandardCharsets;
2219
import java.util.List;
@@ -41,6 +38,7 @@ public final class JsonUtils {
4138
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2SimpleAuthConfig.class, GOOGLE));
4239
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2OryAuthConfig.class, ORY));
4340
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2KeycloakAuthConfig.class, KEYCLOAK));
41+
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2GenericAuthConfig.class, GENERIC));
4442
}
4543

4644
public static final JsonNode EMPTY_JSON_NODE = createObjectNode();

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationController.java

+22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.lowcoder.api.authentication;
22

33
import java.util.List;
4+
import java.util.Map;
45

56
import org.lowcoder.api.authentication.dto.APIKeyRequest;
67
import org.lowcoder.api.authentication.dto.AuthConfigRequest;
@@ -14,7 +15,9 @@
1415
import org.lowcoder.domain.authentication.FindAuthConfig;
1516
import org.lowcoder.domain.user.model.APIKey;
1617
import org.lowcoder.sdk.auth.AbstractAuthConfig;
18+
import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig;
1719
import org.lowcoder.sdk.util.CookieHelper;
20+
import org.springframework.http.ResponseEntity;
1821
import org.springframework.web.bind.annotation.PathVariable;
1922
import org.springframework.web.bind.annotation.RequestBody;
2023
import org.springframework.web.bind.annotation.RequestParam;
@@ -128,4 +131,23 @@ public Mono<ResponseView<List<APIKey>>> getAllAPIKeys() {
128131
.map(ResponseView::success);
129132
}
130133

134+
/**
135+
* This endpoint is to get IDP configuration
136+
* @param issuerUri String
137+
* @param source String
138+
* @param sourceName String
139+
* @param clientId String
140+
* @param clientSecret String
141+
* @return Oauth2GenericAuthConfig
142+
*/
143+
@Override
144+
public Mono<ResponseView<Oauth2GenericAuthConfig>> addOAuthProvider(String issuerUri,
145+
String source,
146+
String sourceName,
147+
String clientId,
148+
String clientSecret) {
149+
return authenticationApiService.fetchAndParseConfiguration(issuerUri, source, sourceName, clientId, clientSecret)
150+
.map(ResponseView::success);
151+
}
152+
131153
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationEndpoints.java

+18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.lowcoder.api.authentication;
22

33
import java.util.List;
4+
import java.util.Map;
45

56
import org.lowcoder.api.authentication.dto.APIKeyRequest;
67
import org.lowcoder.api.authentication.dto.AuthConfigRequest;
@@ -11,8 +12,10 @@
1112
import org.lowcoder.domain.user.model.APIKey;
1213
import org.lowcoder.infra.constant.NewUrl;
1314
import org.lowcoder.sdk.auth.AbstractAuthConfig;
15+
import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig;
1416
import org.lowcoder.sdk.config.SerializeConfig.JsonViews;
1517
import org.lowcoder.sdk.constants.AuthSourceConstants;
18+
import org.springframework.http.ResponseEntity;
1619
import org.springframework.web.bind.annotation.DeleteMapping;
1720
import org.springframework.web.bind.annotation.GetMapping;
1821
import org.springframework.web.bind.annotation.PathVariable;
@@ -160,4 +163,19 @@ public Mono<ResponseView<Boolean>> linkAccountWithThirdParty(
160163
public record FormLoginRequest(String loginId, String password, boolean register, String source, String authId) {
161164
}
162165

166+
/**
167+
* This endpoint is to get IDP configuration
168+
* @param issuerUri String
169+
* @param source String
170+
* @param sourceName String
171+
* @param clientId String
172+
* @param clientSecret String
173+
* @return Oauth2GenericAuthConfig
174+
*/
175+
@GetMapping("/providers")
176+
public Mono<ResponseView<Oauth2GenericAuthConfig>> addOAuthProvider(@RequestParam String issuerUri,
177+
@RequestParam String source,
178+
@RequestParam String sourceName,
179+
@RequestParam String clientId,
180+
@RequestParam String clientSecret);
163181
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.lowcoder.api.authentication.request.oauth2;
2+
3+
import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig;
4+
5+
/**
6+
* This class is the implementation of Oauth2Source and uses an instance of GenericOAuthProviderConfig
7+
* to return the appropriate URLs
8+
*/
9+
public class GenericOAuthProviderSource implements Oauth2Source {
10+
11+
private final Oauth2GenericAuthConfig config;
12+
13+
public GenericOAuthProviderSource(Oauth2GenericAuthConfig config) {
14+
this.config = config;
15+
}
16+
17+
@Override
18+
public String accessToken() {
19+
return config.getTokenEndpoint();
20+
}
21+
22+
@Override
23+
public String userInfo() {
24+
return config.getUserInfoEndpoint();
25+
}
26+
27+
@Override
28+
public String refresh() {
29+
return config.getTokenEndpoint();
30+
}
31+
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/Oauth2AuthRequestFactory.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44

55
import org.lowcoder.api.authentication.request.AuthRequest;
66
import org.lowcoder.api.authentication.request.AuthRequestFactory;
7-
import org.lowcoder.api.authentication.request.oauth2.request.AbstractOauth2Request;
8-
import org.lowcoder.api.authentication.request.oauth2.request.GithubRequest;
9-
import org.lowcoder.api.authentication.request.oauth2.request.GoogleRequest;
10-
import org.lowcoder.api.authentication.request.oauth2.request.KeycloakRequest;
11-
import org.lowcoder.api.authentication.request.oauth2.request.OryRequest;
7+
import org.lowcoder.api.authentication.request.oauth2.request.*;
8+
import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig;
129
import org.lowcoder.sdk.auth.Oauth2KeycloakAuthConfig;
1310
import org.lowcoder.sdk.auth.Oauth2OryAuthConfig;
1411
import org.lowcoder.sdk.auth.Oauth2SimpleAuthConfig;
@@ -32,6 +29,7 @@ private AbstractOauth2Request<? extends Oauth2SimpleAuthConfig> buildRequest(OAu
3229
case GOOGLE -> new GoogleRequest((Oauth2SimpleAuthConfig) context.getAuthConfig());
3330
case ORY -> new OryRequest((Oauth2OryAuthConfig) context.getAuthConfig());
3431
case KEYCLOAK -> new KeycloakRequest((Oauth2KeycloakAuthConfig)context.getAuthConfig());
32+
case GENERIC -> new GenericAuthRequest((Oauth2GenericAuthConfig) context.getAuthConfig());
3533
default -> throw new UnsupportedOperationException(context.getAuthConfig().getAuthType());
3634
};
3735
}
@@ -42,6 +40,7 @@ public Set<String> supportedAuthTypes() {
4240
GITHUB,
4341
GOOGLE,
4442
ORY,
45-
KEYCLOAK);
43+
KEYCLOAK,
44+
GENERIC);
4645
}
4746
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.lowcoder.api.authentication.request.oauth2.request;
2+
3+
import org.lowcoder.api.authentication.request.AuthException;
4+
import org.lowcoder.api.authentication.request.oauth2.GenericOAuthProviderSource;
5+
import org.lowcoder.api.authentication.request.oauth2.OAuth2RequestContext;
6+
import org.lowcoder.domain.user.model.AuthToken;
7+
import org.lowcoder.domain.user.model.AuthUser;
8+
import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig;
9+
import org.lowcoder.sdk.auth.Oauth2KeycloakAuthConfig;
10+
import org.lowcoder.sdk.util.JsonUtils;
11+
import org.lowcoder.sdk.webclient.WebClientBuildHelper;
12+
import org.springframework.http.MediaType;
13+
import org.springframework.web.reactive.function.BodyInserters;
14+
import reactor.core.publisher.Mono;
15+
16+
import java.util.Map;
17+
18+
import static org.lowcoder.api.authentication.util.AuthenticationUtils.mapToAuthToken;
19+
import static org.lowcoder.api.authentication.util.AuthenticationUtils.mapToAuthUser;
20+
21+
/**
22+
* This class is for Generic Auth Request
23+
*/
24+
public class GenericAuthRequest extends AbstractOauth2Request<Oauth2GenericAuthConfig>{
25+
26+
public GenericAuthRequest(Oauth2GenericAuthConfig context) {
27+
super(context, new GenericOAuthProviderSource(context));
28+
}
29+
30+
@Override
31+
protected Mono<AuthToken> getAuthToken(OAuth2RequestContext context) {
32+
return WebClientBuildHelper.builder()
33+
.systemProxy()
34+
.build()
35+
.post()
36+
.uri(config.getTokenEndpoint())
37+
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
38+
.body(BodyInserters.fromFormData("code", context.getCode())
39+
.with("client_id", config.getClientId())
40+
.with("client_secret", config.getClientSecret())
41+
.with("grant_type", "authorization_code")
42+
.with("redirect_uri", context.getRedirectUrl()))
43+
.retrieve()
44+
.bodyToMono(Map.class)
45+
.flatMap(map -> {
46+
if (map.containsKey("error") || map.containsKey("error_description")) {
47+
return Mono.error(new AuthException(JsonUtils.toJson(map)));
48+
}
49+
return Mono.just(mapToAuthToken(map));
50+
});
51+
}
52+
53+
@Override
54+
protected Mono<AuthToken> refreshAuthToken(String refreshToken) {
55+
return WebClientBuildHelper.builder()
56+
.systemProxy()
57+
.build()
58+
.post()
59+
.uri(config.getTokenEndpoint())
60+
.body(BodyInserters.fromFormData("grant_type", "refresh_token")
61+
.with("refresh_token", refreshToken)
62+
.with("client_id", config.getClientId())
63+
.with("client_secret", config.getClientSecret()))
64+
.retrieve()
65+
.bodyToMono(Map.class)
66+
.flatMap(map -> {
67+
if (map.containsKey("error") || map.containsKey("error_description")) {
68+
return Mono.error(new AuthException(JsonUtils.toJson(map)));
69+
}
70+
return Mono.just(mapToAuthToken(map));
71+
});
72+
}
73+
74+
@Override
75+
protected Mono<AuthUser> getAuthUser(AuthToken authToken) {
76+
return WebClientBuildHelper.builder()
77+
.systemProxy()
78+
.build()
79+
.get()
80+
.uri(config.getUserInfoEndpoint())
81+
.headers(headers -> headers.setBearerAuth(authToken.getAccessToken()))
82+
.retrieve()
83+
.bodyToMono(Map.class)
84+
.flatMap(map -> {
85+
if (map.containsKey("error") || map.containsKey("error_description")) {
86+
return Mono.error(new AuthException(JsonUtils.toJson(map)));
87+
}
88+
return Mono.just(mapToAuthUser(map));
89+
});
90+
}
91+
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiService.java

+16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.lowcoder.domain.authentication.FindAuthConfig;
77
import org.lowcoder.domain.user.model.APIKey;
88
import org.lowcoder.domain.user.model.AuthUser;
9+
import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig;
910
import org.springframework.web.server.ServerWebExchange;
1011
import reactor.core.publisher.Flux;
1112
import reactor.core.publisher.Mono;
@@ -29,4 +30,19 @@ public interface AuthenticationApiService {
2930
Mono<Void> deleteAPIKey(String authId);
3031

3132
Flux<APIKey> findAPIKeys();
33+
34+
/**
35+
* This method is to fetch and parse the OpenID configuration from the issuer URI.
36+
* @param issuerUri String
37+
* @param source String
38+
* @param sourceName String
39+
* @param clientId String
40+
* @param clientSecret String
41+
* @return Oauth2GenericAuthConfig
42+
*/
43+
Mono<Oauth2GenericAuthConfig> fetchAndParseConfiguration(String issuerUri,
44+
String source,
45+
String sourceName,
46+
String clientId,
47+
String clientSecret);
3248
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java

+52
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@
3131
import org.lowcoder.domain.user.model.*;
3232
import org.lowcoder.domain.user.service.UserService;
3333
import org.lowcoder.sdk.auth.AbstractAuthConfig;
34+
import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig;
35+
import org.lowcoder.sdk.auth.constants.AuthTypeConstants;
3436
import org.lowcoder.sdk.config.AuthProperties;
3537
import org.lowcoder.sdk.exception.BizError;
3638
import org.lowcoder.sdk.exception.BizException;
3739
import org.lowcoder.sdk.util.CookieHelper;
40+
import org.lowcoder.sdk.webclient.WebClientBuildHelper;
3841
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
3942
import org.springframework.stereotype.Service;
4043
import org.springframework.web.server.ServerWebExchange;
@@ -330,6 +333,55 @@ public Flux<APIKey> findAPIKeys() {
330333
);
331334
}
332335

336+
/**
337+
* This method is to fetch and parse the OpenID configuration from the issuer URI.
338+
* @param issuerUri String
339+
* @param source String
340+
* @param sourceName String
341+
* @param clientId String
342+
* @param clientSecret String
343+
* @return Oauth2GenericAuthConfig
344+
*/
345+
@Override
346+
public Mono<Oauth2GenericAuthConfig> fetchAndParseConfiguration(String issuerUri,
347+
String source,
348+
String sourceName,
349+
String clientId,
350+
String clientSecret) {
351+
String wellKnownUri = issuerUri + "/.well-known/openid-configuration";
352+
return WebClientBuildHelper.builder()
353+
.systemProxy()
354+
.build()
355+
.get()
356+
.uri(wellKnownUri)
357+
.retrieve()
358+
.bodyToMono(Map.class)
359+
.map(map -> mapToConfig(map, source, sourceName, clientId, clientSecret));
360+
}
361+
362+
/**
363+
* This method is to map to config for Generic Auth Provider
364+
* @param map Object that comes from /.well-known endpoint for IDP Configuration
365+
* @return Oauth2GenericAuthConfig
366+
*/
367+
private Oauth2GenericAuthConfig mapToConfig(Map<String, Object> map,
368+
String source,
369+
String sourceName,
370+
String clientId,
371+
String clientSecret) {
372+
return Oauth2GenericAuthConfig.builder()
373+
.authType(AuthTypeConstants.GENERIC)
374+
.source(source)
375+
.sourceName(sourceName)
376+
.clientId(clientId)
377+
.clientSecret(clientSecret)
378+
.issuerUri((String) map.get("issuer"))
379+
.authorizationEndpoint((String) map.get("authorization_endpoint"))
380+
.tokenEndpoint((String) map.get("token_endpoint"))
381+
.userInfoEndpoint((String) map.get("userinfo_endpoint"))
382+
.build();
383+
}
384+
333385

334386
private Mono<Void> removeTokensByAuthId(String authId) {
335387
return sessionUserService.getVisitorOrgMemberCache()

0 commit comments

Comments
 (0)