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:
Arpit Mohan 2019-09-09 05:16:42 +00:00
commit a03c0a8bde
7 changed files with 44 additions and 11 deletions

View File

@ -3,6 +3,8 @@ package com.appsmith.server.configurations;
import com.appsmith.server.domains.LoginSource;
import com.appsmith.server.domains.User;
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.UserService;
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.web.server.ServerOAuth2AuthorizedClientRepository;
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.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
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
* of plugging a handler for the same purpose.
*/
@Configuration
@Component
public class ClientUserRepository implements ServerOAuth2AuthorizedClientRepository {
private static final String DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME =
WebSessionServerOAuth2AuthorizedClientRepository.class.getName() + ".AUTHORIZED_CLIENTS";
private final String sessionAttributeName = DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME;
UserService userService;
OrganizationService organizationService;
public ClientUserRepository(UserService userService, OrganizationService organizationService) {
CommonConfig commonConfig;
public ClientUserRepository(UserService userService, OrganizationService organizationService, CommonConfig commonConfig) {
this.userService = userService;
this.organizationService = organizationService;
this.commonConfig = commonConfig;
}
@Override
@ -69,6 +79,19 @@ public class ClientUserRepository implements ServerOAuth2AuthorizedClientReposit
ServerWebExchange exchange) {
Assert.notNull(authorizedClient, "authorizedClient 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()
.doOnSuccess(session -> {
Map<String, OAuth2AuthorizedClient> authorizedClients = getAuthorizedClients(session);

View File

@ -1,5 +1,8 @@
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.Configuration;
import reactor.core.scheduler.Scheduler;
@ -7,12 +10,18 @@ import reactor.core.scheduler.Schedulers;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.List;
@Getter
@Setter
@Configuration
public class CommonConfig {
private String ELASTIC_THREAD_POOL_NAME = "appsmith-elastic-pool";
@Value("#{'${oauth2.allowed-domains}'.split(',')}")
private List<String> allowedDomains;
@Bean
public Scheduler scheduler() {
return Schedulers.newElastic(ELASTIC_THREAD_POOL_NAME);

View File

@ -29,6 +29,9 @@ public class SecurityConfig {
@Autowired
private OrganizationService organizationService;
@Autowired
private CommonConfig commonConfig;
/**
* This configuration enables CORS requests for the most common HTTP Methods
*
@ -72,7 +75,7 @@ public class SecurityConfig {
.authenticated()
.and().httpBasic()
.and().oauth2Login()
.authorizedClientRepository(new ClientUserRepository(userService, organizationService))
.authorizedClientRepository(new ClientUserRepository(userService, organizationService, commonConfig))
.and().formLogin()
.and().build();
}

View File

@ -12,6 +12,7 @@ public enum AppsmithError {
PLUGIN_NOT_INSTALLED(400, 4001, "Plugin {0} not installed"),
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"),
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");
private Integer httpErrorCode;

View File

@ -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-secret=9dvITt4OayEY1HfeY8bHX74p
#Hard coded organization properties
organization.id=5d64f1f594a5d31b3ee9ca16
# Accounts from specific domains are allowed to login
oauth2.allowed-domains=appsmith.com

View File

@ -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-secret=9dvITt4OayEY1HfeY8bHX74p
#Hard coded organization properties
organization.id=5d3e90a2dfec7c00047a81ea
# Accounts from specific domains are allowed to login
oauth2.allowed-domains=appsmith.com

View File

@ -12,7 +12,4 @@ spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
spring.jpa.show-sql=true
# Jackson Properties
spring.jackson.default-property-inclusion=non_null
#Hard coded organization properties
organization.id=5d5e7c29006cb4e82ea75075
spring.jackson.default-property-inclusion=non_null