Merge branch 'feature/oauth2-custom-domain' into 'master'
A hacky approach to limiting the Google OAuth2 to a set of custom domains. Closes #18 See merge request theappsmith/internal-tools-server!12
This commit is contained in:
commit
a03c0a8bde
|
|
@ -3,6 +3,8 @@ package com.appsmith.server.configurations;
|
||||||
import com.appsmith.server.domains.LoginSource;
|
import com.appsmith.server.domains.LoginSource;
|
||||||
import com.appsmith.server.domains.User;
|
import com.appsmith.server.domains.User;
|
||||||
import com.appsmith.server.domains.UserState;
|
import com.appsmith.server.domains.UserState;
|
||||||
|
import com.appsmith.server.exceptions.AppsmithError;
|
||||||
|
import com.appsmith.server.exceptions.AppsmithException;
|
||||||
import com.appsmith.server.services.OrganizationService;
|
import com.appsmith.server.services.OrganizationService;
|
||||||
import com.appsmith.server.services.UserService;
|
import com.appsmith.server.services.UserService;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
@ -10,7 +12,9 @@ import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||||
import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository;
|
import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.WebSession;
|
import org.springframework.web.server.WebSession;
|
||||||
|
|
@ -39,18 +43,24 @@ import java.util.Map;
|
||||||
* saveAuthorizedClient is called on every successful OAuth2 authentication, this solves the problem
|
* saveAuthorizedClient is called on every successful OAuth2 authentication, this solves the problem
|
||||||
* of plugging a handler for the same purpose.
|
* of plugging a handler for the same purpose.
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Component
|
||||||
public class ClientUserRepository implements ServerOAuth2AuthorizedClientRepository {
|
public class ClientUserRepository implements ServerOAuth2AuthorizedClientRepository {
|
||||||
|
|
||||||
private static final String DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME =
|
private static final String DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME =
|
||||||
WebSessionServerOAuth2AuthorizedClientRepository.class.getName() + ".AUTHORIZED_CLIENTS";
|
WebSessionServerOAuth2AuthorizedClientRepository.class.getName() + ".AUTHORIZED_CLIENTS";
|
||||||
|
|
||||||
private final String sessionAttributeName = DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME;
|
private final String sessionAttributeName = DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME;
|
||||||
|
|
||||||
UserService userService;
|
UserService userService;
|
||||||
|
|
||||||
OrganizationService organizationService;
|
OrganizationService organizationService;
|
||||||
|
|
||||||
public ClientUserRepository(UserService userService, OrganizationService organizationService) {
|
CommonConfig commonConfig;
|
||||||
|
|
||||||
|
public ClientUserRepository(UserService userService, OrganizationService organizationService, CommonConfig commonConfig) {
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.organizationService = organizationService;
|
this.organizationService = organizationService;
|
||||||
|
this.commonConfig = commonConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -69,6 +79,19 @@ public class ClientUserRepository implements ServerOAuth2AuthorizedClientReposit
|
||||||
ServerWebExchange exchange) {
|
ServerWebExchange exchange) {
|
||||||
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
|
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
|
||||||
Assert.notNull(exchange, "exchange cannot be null");
|
Assert.notNull(exchange, "exchange cannot be null");
|
||||||
|
Assert.notNull(principal, "authentication object cannot be null");
|
||||||
|
|
||||||
|
// Check if the list of configured custom domains match the authenticated principal.
|
||||||
|
// This is to provide more control over which accounts can be used to access the application.
|
||||||
|
// TODO: This is not a good way to do this. Ideally, we should pass "hd=example.com" to OAuth2 provider to list relevant accounts only
|
||||||
|
if(!commonConfig.getAllowedDomains().isEmpty()) {
|
||||||
|
DefaultOidcUser userPrincipal = (DefaultOidcUser) principal.getPrincipal();
|
||||||
|
String domain = (String) userPrincipal.getAttributes().getOrDefault("hd", "");
|
||||||
|
if(!commonConfig.getAllowedDomains().contains(domain)) {
|
||||||
|
return Mono.error(new AppsmithException(AppsmithError.UNAUTHORIZED_DOMAIN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return exchange.getSession()
|
return exchange.getSession()
|
||||||
.doOnSuccess(session -> {
|
.doOnSuccess(session -> {
|
||||||
Map<String, OAuth2AuthorizedClient> authorizedClients = getAuthorizedClients(session);
|
Map<String, OAuth2AuthorizedClient> authorizedClients = getAuthorizedClients(session);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
package com.appsmith.server.configurations;
|
package com.appsmith.server.configurations;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import reactor.core.scheduler.Scheduler;
|
import reactor.core.scheduler.Scheduler;
|
||||||
|
|
@ -7,12 +10,18 @@ import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
import javax.validation.Validation;
|
import javax.validation.Validation;
|
||||||
import javax.validation.Validator;
|
import javax.validation.Validator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
@Configuration
|
@Configuration
|
||||||
public class CommonConfig {
|
public class CommonConfig {
|
||||||
|
|
||||||
private String ELASTIC_THREAD_POOL_NAME = "appsmith-elastic-pool";
|
private String ELASTIC_THREAD_POOL_NAME = "appsmith-elastic-pool";
|
||||||
|
|
||||||
|
@Value("#{'${oauth2.allowed-domains}'.split(',')}")
|
||||||
|
private List<String> allowedDomains;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Scheduler scheduler() {
|
public Scheduler scheduler() {
|
||||||
return Schedulers.newElastic(ELASTIC_THREAD_POOL_NAME);
|
return Schedulers.newElastic(ELASTIC_THREAD_POOL_NAME);
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ public class SecurityConfig {
|
||||||
@Autowired
|
@Autowired
|
||||||
private OrganizationService organizationService;
|
private OrganizationService organizationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CommonConfig commonConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This configuration enables CORS requests for the most common HTTP Methods
|
* This configuration enables CORS requests for the most common HTTP Methods
|
||||||
*
|
*
|
||||||
|
|
@ -72,7 +75,7 @@ public class SecurityConfig {
|
||||||
.authenticated()
|
.authenticated()
|
||||||
.and().httpBasic()
|
.and().httpBasic()
|
||||||
.and().oauth2Login()
|
.and().oauth2Login()
|
||||||
.authorizedClientRepository(new ClientUserRepository(userService, organizationService))
|
.authorizedClientRepository(new ClientUserRepository(userService, organizationService, commonConfig))
|
||||||
.and().formLogin()
|
.and().formLogin()
|
||||||
.and().build();
|
.and().build();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ public enum AppsmithError {
|
||||||
PLUGIN_NOT_INSTALLED(400, 4001, "Plugin {0} not installed"),
|
PLUGIN_NOT_INSTALLED(400, 4001, "Plugin {0} not installed"),
|
||||||
PLUGIN_ID_NOT_GIVEN(400, 4002, "Missing plugin id. Please input correct plugin id"),
|
PLUGIN_ID_NOT_GIVEN(400, 4002, "Missing plugin id. Please input correct plugin id"),
|
||||||
RESOURCE_ID_NOT_GIVEN(400, 4003, "Missing resource id. Please input correct resource id"),
|
RESOURCE_ID_NOT_GIVEN(400, 4003, "Missing resource id. Please input correct resource id"),
|
||||||
|
UNAUTHORIZED_DOMAIN(401, 4001, "Invalid email domain provided. Please sign in with a valid work email ID"),
|
||||||
INTERNAL_SERVER_ERROR(500, 5000, "Internal server error while processing request");
|
INTERNAL_SERVER_ERROR(500, 5000, "Internal server error while processing request");
|
||||||
|
|
||||||
private Integer httpErrorCode;
|
private Integer httpErrorCode;
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,5 @@ jdbc.postgres.password=root
|
||||||
spring.security.oauth2.client.registration.google.client-id=869021686091-9b84bbf7ea683t1aaefqnmefcnmk6fq6.apps.googleusercontent.com
|
spring.security.oauth2.client.registration.google.client-id=869021686091-9b84bbf7ea683t1aaefqnmefcnmk6fq6.apps.googleusercontent.com
|
||||||
spring.security.oauth2.client.registration.google.client-secret=9dvITt4OayEY1HfeY8bHX74p
|
spring.security.oauth2.client.registration.google.client-secret=9dvITt4OayEY1HfeY8bHX74p
|
||||||
|
|
||||||
#Hard coded organization properties
|
# Accounts from specific domains are allowed to login
|
||||||
organization.id=5d64f1f594a5d31b3ee9ca16
|
oauth2.allowed-domains=appsmith.com
|
||||||
|
|
@ -17,5 +17,5 @@ jdbc.postgres.password=09275163cd7e737baf4c210b5e8db8ed88ddb7a0ee9acc82416fd7534
|
||||||
spring.security.oauth2.client.registration.google.client-id=869021686091-9b84bbf7ea683t1aaefqnmefcnmk6fq6.apps.googleusercontent.com
|
spring.security.oauth2.client.registration.google.client-id=869021686091-9b84bbf7ea683t1aaefqnmefcnmk6fq6.apps.googleusercontent.com
|
||||||
spring.security.oauth2.client.registration.google.client-secret=9dvITt4OayEY1HfeY8bHX74p
|
spring.security.oauth2.client.registration.google.client-secret=9dvITt4OayEY1HfeY8bHX74p
|
||||||
|
|
||||||
#Hard coded organization properties
|
# Accounts from specific domains are allowed to login
|
||||||
organization.id=5d3e90a2dfec7c00047a81ea
|
oauth2.allowed-domains=appsmith.com
|
||||||
|
|
@ -12,7 +12,4 @@ spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=true
|
||||||
|
|
||||||
# Jackson Properties
|
# Jackson Properties
|
||||||
spring.jackson.default-property-inclusion=non_null
|
spring.jackson.default-property-inclusion=non_null
|
||||||
|
|
||||||
#Hard coded organization properties
|
|
||||||
organization.id=5d5e7c29006cb4e82ea75075
|
|
||||||
Loading…
Reference in New Issue
Block a user