Merge branch 'trisha-dev' into 'master'
Oauth2 implementation - #17 See merge request theappsmith/internal-tools-server!3
This commit is contained in:
commit
659f3eab54
|
|
@ -0,0 +1,125 @@
|
|||
package com.mobtools.server.configurations;
|
||||
|
||||
import com.mobtools.server.domains.LoginSource;
|
||||
import com.mobtools.server.domains.User;
|
||||
import com.mobtools.server.domains.UserState;
|
||||
import com.mobtools.server.repositories.UserRepository;
|
||||
import com.mobtools.server.services.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.OidcUser;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* This code has been copied from WebSessionServerOAuth2AuthorizedClientRepository.java
|
||||
* which also implements ServerOAuth2AuthorizedClientRepository. This was done to make changes
|
||||
* to saveAuthorizedClient to also handle adding users to UserRepository.
|
||||
*
|
||||
* This was done because on authorization, the user needs to be stored in appsmith domain.
|
||||
* To achieve this, saveAuthorizedClient function has been edited in the following manner.
|
||||
* In the reactive flow, post doOnSuccess transformation, another Mono.then has been added. In this,
|
||||
* Authentication object is passed to checkAndCreateUser function. This object is used to get OidcUser from which
|
||||
* user attributes like name, email, etc are extracted. If the user doesnt exist in User
|
||||
* Repository, a new user is created and saved.
|
||||
*
|
||||
* The ClientUserRepository is created during SecurityWebFilterChain Bean creation. By
|
||||
* configuring to use Oauth2Login, this ServerOAuth2AuthorizedClientRepository implementation
|
||||
* is injected. This hack is used to ensure that on successful authentication, we are able
|
||||
* to record the user in our database. Since ServerOAuth2AuthorizedClientRepository's
|
||||
* saveAuthorizedClient is called on every successful OAuth2 authentication, this solves the problem
|
||||
* of plugging a handler for the same purpose.
|
||||
*/
|
||||
public class ClientUserRepository implements ServerOAuth2AuthorizedClientRepository {
|
||||
|
||||
UserService userService;
|
||||
|
||||
public ClientUserRepository(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
private static final String DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME =
|
||||
WebSessionServerOAuth2AuthorizedClientRepository.class.getName() + ".AUTHORIZED_CLIENTS";
|
||||
private final String sessionAttributeName = DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends OAuth2AuthorizedClient> Mono<T> loadAuthorizedClient(String clientRegistrationId, Authentication principal,
|
||||
ServerWebExchange exchange) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
Assert.notNull(exchange, "exchange cannot be null");
|
||||
return exchange.getSession()
|
||||
.map(this::getAuthorizedClients)
|
||||
.flatMap(clients -> Mono.justOrEmpty((T) clients.get(clientRegistrationId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal,
|
||||
ServerWebExchange exchange) {
|
||||
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
|
||||
Assert.notNull(exchange, "exchange cannot be null");
|
||||
return exchange.getSession()
|
||||
.doOnSuccess(session -> {
|
||||
Map<String, OAuth2AuthorizedClient> authorizedClients = getAuthorizedClients(session);
|
||||
authorizedClients.put(authorizedClient.getClientRegistration().getRegistrationId(), authorizedClient);
|
||||
session.getAttributes().put(this.sessionAttributeName, authorizedClients);
|
||||
})
|
||||
/*
|
||||
* TODO
|
||||
* Need to test how this behaves in the following :
|
||||
* 1. Clustered environment
|
||||
* 2. Redis saved sessions
|
||||
*/
|
||||
.then(checkAndCreateUser((OidcUser) principal.getPrincipal()))
|
||||
.then(Mono.empty());
|
||||
}
|
||||
|
||||
private Mono<User> checkAndCreateUser(OidcUser user) {
|
||||
User newUser = new User();
|
||||
newUser.setName(user.getFullName());
|
||||
newUser.setEmail(user.getEmail());
|
||||
newUser.setSource(LoginSource.GOOGLE);
|
||||
newUser.setState(UserState.ACTIVATED);
|
||||
newUser.setIsEnabled(true);
|
||||
|
||||
return userService.findByEmail(user.getEmail())
|
||||
.switchIfEmpty(userService.save(newUser));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> removeAuthorizedClient(String clientRegistrationId, Authentication principal,
|
||||
ServerWebExchange exchange) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
Assert.notNull(exchange, "exchange cannot be null");
|
||||
return exchange.getSession()
|
||||
.doOnSuccess(session -> {
|
||||
Map<String, OAuth2AuthorizedClient> authorizedClients = getAuthorizedClients(session);
|
||||
authorizedClients.remove(clientRegistrationId);
|
||||
if (authorizedClients.isEmpty()) {
|
||||
session.getAttributes().remove(this.sessionAttributeName);
|
||||
} else {
|
||||
session.getAttributes().put(this.sessionAttributeName, authorizedClients);
|
||||
}
|
||||
})
|
||||
.then(Mono.empty());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, OAuth2AuthorizedClient> getAuthorizedClients(WebSession session) {
|
||||
Map<String, OAuth2AuthorizedClient> authorizedClients = session == null ? null :
|
||||
(Map<String, OAuth2AuthorizedClient>) session.getAttribute(this.sessionAttributeName);
|
||||
if (authorizedClients == null) {
|
||||
authorizedClients = new HashMap<>();
|
||||
}
|
||||
return authorizedClients;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,10 @@ package com.mobtools.server.configurations;
|
|||
|
||||
|
||||
import com.mobtools.server.constants.Security;
|
||||
import com.mobtools.server.repositories.UserRepository;
|
||||
import com.mobtools.server.services.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
|
|
@ -16,16 +17,15 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
|
|||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.reactive.CorsWebFilter;
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@EnableReactiveMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* This configuration enables CORS requests for the most common HTTP Methods
|
||||
*
|
||||
|
|
@ -68,6 +68,9 @@ public class SecurityConfig {
|
|||
.anyExchange()
|
||||
.authenticated()
|
||||
.and().httpBasic()
|
||||
.and().oauth2Login()
|
||||
.authorizedClientRepository(new ClientUserRepository(userService))
|
||||
.and().formLogin()
|
||||
.and().build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package com.mobtools.server.domains;
|
||||
|
||||
public enum LoginSource {
|
||||
GOOGLE, FORM
|
||||
}
|
||||
|
|
@ -5,8 +5,14 @@ import lombok.NoArgsConstructor;
|
|||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Getter
|
||||
|
|
@ -14,7 +20,7 @@ import java.util.Set;
|
|||
@ToString
|
||||
@NoArgsConstructor
|
||||
@Document
|
||||
public class User extends BaseDomain {
|
||||
public class User extends BaseDomain implements UserDetails {
|
||||
|
||||
private String name;
|
||||
|
||||
|
|
@ -23,4 +29,49 @@ public class User extends BaseDomain {
|
|||
private Set<Role> roles;
|
||||
|
||||
private String password;
|
||||
|
||||
private LoginSource source;
|
||||
|
||||
private UserState state;
|
||||
|
||||
private Boolean isEnabled;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
|
||||
if (roles == null || roles.isEmpty()) //No existing roles found.
|
||||
return null;
|
||||
|
||||
Collection<SimpleGrantedAuthority> authorities = roles.stream()
|
||||
.map(role -> new SimpleGrantedAuthority(role.toString()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package com.mobtools.server.domains;
|
||||
|
||||
public enum UserState {
|
||||
NEW, INVITED, ACTIVATED
|
||||
}
|
||||
|
|
@ -8,4 +8,5 @@ import reactor.core.publisher.Mono;
|
|||
public interface UserRepository extends BaseRepository<User, String> {
|
||||
|
||||
Mono<User> findByName(String name);
|
||||
Mono<User> findByEmail(String email);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,4 +6,6 @@ import reactor.core.publisher.Mono;
|
|||
public interface UserService extends CrudService<User, String> {
|
||||
|
||||
Mono<User> findByUsername(String name);
|
||||
Mono<User> findByEmail(String email);
|
||||
Mono<User> save(User newUser);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@ import com.mobtools.server.domains.User;
|
|||
import com.mobtools.server.repositories.UserRepository;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
|
||||
@Service
|
||||
public class UserServiceImpl extends BaseService<UserRepository, User, String> implements UserService {
|
||||
public class UserServiceImpl extends BaseService<UserRepository, User, String> implements UserService, UserDetailsService {
|
||||
|
||||
private UserRepository repository;
|
||||
|
||||
|
|
@ -25,4 +28,19 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
public Mono<User> findByUsername(String name) {
|
||||
return repository.findByName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> findByEmail(String email) {
|
||||
return repository.findByEmail(email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> save(User user) {
|
||||
return repository.save(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
return repository.findByName(username).block();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,3 +12,8 @@ jdbc.postgres.driver=org.postgresql.Driver
|
|||
jdbc.postgres.url=jdbc:postgresql://localhost/mobtools
|
||||
jdbc.postgres.username=postgres
|
||||
jdbc.postgres.password=root
|
||||
|
||||
#Spring security
|
||||
spring.security.oauth2.client.registration.google.client-id=869021686091-9b84bbf7ea683t1aaefqnmefcnmk6fq6.apps.googleusercontent.com
|
||||
spring.security.oauth2.client.registration.google.client-secret=9dvITt4OayEY1HfeY8bHX74p
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,8 @@ logging.level.com.mobtools=debug
|
|||
jdbc.postgres.driver=org.postgresql.Driver
|
||||
jdbc.postgres.url=jdbc:postgresql://ec2-54-247-85-251.eu-west-1.compute.amazonaws.com/d266aalso50024
|
||||
jdbc.postgres.username=pornlzmggggpgk
|
||||
jdbc.postgres.password=09275163cd7e737baf4c210b5e8db8ed88ddb7a0ee9acc82416fd75346ea4bbb
|
||||
jdbc.postgres.password=09275163cd7e737baf4c210b5e8db8ed88ddb7a0ee9acc82416fd75346ea4bbb
|
||||
|
||||
#Spring security
|
||||
spring.security.oauth2.client.registration.google.client-id=869021686091-9b84bbf7ea683t1aaefqnmefcnmk6fq6.apps.googleusercontent.com
|
||||
spring.security.oauth2.client.registration.google.client-secret=9dvITt4OayEY1HfeY8bHX74p
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@ spring.jpa.show-sql=true
|
|||
|
||||
# Jackson Properties
|
||||
spring.jackson.default-property-inclusion=non_null
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user