Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rest): Added creating user from REST and basic authentication #1832

Merged
merged 1 commit into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.apache.logging.log4j.Logger;
import org.apache.thrift.TException;
import org.eclipse.sw360.datahandler.common.DatabaseSettings;
import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary;
import org.eclipse.sw360.datahandler.thrift.PaginationData;
import org.eclipse.sw360.datahandler.thrift.RequestStatus;
import org.eclipse.sw360.datahandler.thrift.users.User;
Expand All @@ -30,6 +31,7 @@

import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotEmpty;
import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotNull;
import static org.eclipse.sw360.datahandler.common.SW360Assert.assertUser;

/**
* Implementation of the Thrift service
Expand Down Expand Up @@ -101,9 +103,8 @@ public List<User> getAllUsers() {
}

@Override
public RequestStatus addUser(User user) throws TException {
assertNotNull(user);
assertNotNull(user.getEmail());
public AddDocumentRequestSummary addUser(User user) throws TException {
assertUser(user);
return db.addUser(user);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
package org.eclipse.sw360.users.db;

import org.eclipse.sw360.datahandler.cloudantclient.DatabaseConnectorCloudant;
import org.eclipse.sw360.datahandler.common.CommonUtils;
import org.eclipse.sw360.datahandler.common.DatabaseSettings;
import org.eclipse.sw360.datahandler.couchdb.DatabaseConnector;
import org.eclipse.sw360.datahandler.db.UserRepository;
import org.eclipse.sw360.datahandler.db.UserSearchHandler;
import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus;
import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary;
import org.eclipse.sw360.datahandler.thrift.PaginationData;
import org.eclipse.sw360.datahandler.thrift.RequestStatus;
import org.eclipse.sw360.datahandler.thrift.SW360Exception;
Expand All @@ -39,6 +42,8 @@
*/
public class UserDatabaseHandler {

private static final String LAST_NAME_IS_MANDATORY = "Last Name is mandatory";
private static final String GIVEN_NAME_IS_MANDATORY = "Given Name is mandatory";
/**
* Connection to the couchDB database
*/
Expand Down Expand Up @@ -76,12 +81,24 @@ private void prepareUser(User user) throws SW360Exception {
ThriftValidate.prepareUser(user);
}

public RequestStatus addUser(User user) throws SW360Exception {
public AddDocumentRequestSummary addUser(User user) throws SW360Exception {
prepareUser(user);
AddDocumentRequestSummary addDocReqSummarry = new AddDocumentRequestSummary();
if (CommonUtils.isNullEmptyOrWhitespace(user.getGivenname())) {
return addDocReqSummarry.setMessage(GIVEN_NAME_IS_MANDATORY).setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT);
} else if (CommonUtils.isNullEmptyOrWhitespace(user.getLastname())) {
return addDocReqSummarry.setMessage(LAST_NAME_IS_MANDATORY).setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT);
}

User existingUserInDB = getByEmail(user.getEmail());
if (null != existingUserInDB) {
return addDocReqSummarry.setId(existingUserInDB.getId())
.setRequestStatus(AddDocumentRequestStatus.DUPLICATE);
}
// Add to database
db.add(user);

return RequestStatus.SUCCESS;
return addDocReqSummarry.setId(user.getId()).setRequestStatus(AddDocumentRequestStatus.SUCCESS);
}

public RequestStatus updateUser(User user) throws SW360Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@


public class UserHandlerTest {
private static final String DUMMY_LASTNAME = "Dummy Lastname";

private static final String DUMMY_GIVENNAME = "Dummy Givenname";

private static final String dbName = DatabaseSettingsTest.COUCH_DB_USERS;

private static final String DUMMY_EMAIL_ADDRESS_1 = "[email protected]";
Expand All @@ -47,7 +51,7 @@ public void tearDown() throws Exception {

@Test
public void testAddUser() throws Exception {
User userWithComment = new User().setEmail(DUMMY_EMAIL_ADDRESS_1).setCommentMadeDuringModerationRequest(DUMMY_COMMENT);
User userWithComment = new User().setEmail(DUMMY_EMAIL_ADDRESS_1).setCommentMadeDuringModerationRequest(DUMMY_COMMENT).setGivenname(DUMMY_GIVENNAME).setLastname(DUMMY_LASTNAME).setDepartment(DUMMY_DEPARTMENT);

handler.addUser(userWithComment);

Expand All @@ -58,7 +62,7 @@ public void testAddUser() throws Exception {

@Test
public void testUpdateUser() throws Exception {
User userWithoutComment = new User().setEmail(DUMMY_EMAIL_ADDRESS_2);
User userWithoutComment = new User().setEmail(DUMMY_EMAIL_ADDRESS_2).setGivenname(DUMMY_GIVENNAME).setLastname(DUMMY_LASTNAME).setDepartment(DUMMY_DEPARTMENT);

handler.addUser(userWithoutComment); // does not contain a comment

Expand Down
4 changes: 3 additions & 1 deletion libraries/datahandler/src/main/thrift/users.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ include "sw360.thrift"
namespace java org.eclipse.sw360.datahandler.thrift.users
namespace php sw360.thrift.users

typedef sw360.AddDocumentRequestSummary AddDocumentRequestSummary
typedef sw360.RequestStatus RequestStatus
typedef sw360.PaginationData PaginationData

Expand Down Expand Up @@ -68,6 +69,7 @@ struct User {
23: optional list<string> primaryRoles,
24: optional bool deactivated
25: optional map<string, ClientMetadata> oidcClientInfos,
26: optional string password
}

struct ClientMetadata {
Expand Down Expand Up @@ -123,7 +125,7 @@ service UserService {
/**
* add SW360-user to database, user.email is used as id
**/
RequestStatus addUser(1: User user);
AddDocumentRequestSummary addUser(1: User user);

/**
* update SW360-user in database
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public List<GrantedAuthority> generateFromUser(User user) {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

grantedAuthorities.add(new SimpleGrantedAuthority(READ.getAuthority()));
if(user != null) {
if (user != null) {
if (PermissionUtils.isUserAtLeast(Sw360AuthorizationServer.CONFIG_WRITE_ACCESS_USERGROUP, user)) {
grantedAuthorities.add(new SimpleGrantedAuthority(Sw360GrantedAuthority.WRITE.getAuthority()));
}
Expand Down
3 changes: 3 additions & 0 deletions rest/resource-server/src/docs/asciidoc/api-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ Token response elements
| `Write Access`
| rest.write.access.usergroup
| SW360_ADMIN
| `Admin Access`
| rest.admin.access.usergroup
| SW360_ADMIN
| `Enable Token Generator`
| rest.apitoken.generator.enable
| false
Expand Down
13 changes: 11 additions & 2 deletions rest/resource-server/src/docs/asciidoc/users.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,16 @@ include::{snippets}/should_document_get_user/links.adoc[]
[[resources-users-create]]
==== Creating a user

Creating a user is currently not supported by the REST API.
A `POST` request will result in an error.
A `POST` request will create a user(not in Liferay).

===== Request structure
include::{snippets}/should_document_create_user/request-fields.adoc[]

===== Response structure
include::{snippets}/should_document_create_user/response-fields.adoc[]

===== Example request
include::{snippets}/should_document_create_user/curl-request.adoc[]

===== Example response
include::{snippets}/should_document_create_user/http-response.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.Set;

import org.eclipse.sw360.datahandler.common.CommonUtils;
import org.eclipse.sw360.datahandler.thrift.users.UserGroup;
import org.eclipse.sw360.rest.common.PropertyUtils;
import org.eclipse.sw360.rest.common.Sw360CORSFilter;
import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper;
Expand Down Expand Up @@ -55,6 +56,10 @@ public class Sw360ResourceServer extends SpringBootServletInitializer {
public static final String JWKS_ENDPOINT_URL;
public static final Boolean IS_JWKS_VALIDATION_ENABLED;
public static final Boolean IS_FORCE_UPDATE_ENABLED;
public static final UserGroup CONFIG_WRITE_ACCESS_USERGROUP;
public static final UserGroup CONFIG_ADMIN_ACCESS_USERGROUP;
private static final String DEFAULT_WRITE_ACCESS_USERGROUP = UserGroup.SW360_ADMIN.name();
private static final String DEFAULT_ADMIN_ACCESS_USERGROUP = UserGroup.SW360_ADMIN.name();

static {
Properties props = CommonUtils.loadProperties(Sw360ResourceServer.class, SW360_PROPERTIES_FILE_PATH);
Expand All @@ -69,6 +74,8 @@ public class Sw360ResourceServer extends SpringBootServletInitializer {
IS_JWKS_VALIDATION_ENABLED = Boolean.parseBoolean(props.getProperty("jwks.validation.enabled", "false"));
IS_FORCE_UPDATE_ENABLED = Boolean.parseBoolean(
System.getProperty("RunRestForceUpdateTest", props.getProperty("rest.force.update.enabled", "false")));
CONFIG_WRITE_ACCESS_USERGROUP = UserGroup.valueOf(props.getProperty("rest.write.access.usergroup", DEFAULT_WRITE_ACCESS_USERGROUP));
CONFIG_ADMIN_ACCESS_USERGROUP = UserGroup.valueOf(props.getProperty("rest.admin.access.usergroup", DEFAULT_ADMIN_ACCESS_USERGROUP));
akapti marked this conversation as resolved.
Show resolved Hide resolved
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ static abstract class EmbeddedProjectMixin extends ProjectMixin {
@JsonIgnoreProperties({
"id",
"revision",
"externalid",
"setPassword",
"wantsMailNotification",
"setWantsMailNotification",
"setId",
Expand Down Expand Up @@ -301,6 +301,10 @@ static abstract class UserMixin extends User {
@Override
@JsonProperty("lastName")
abstract public String getLastname();

@Override
@JsonProperty(access = Access.WRITE_ONLY)
abstract public String getPassword();
}

@JsonInclude(JsonInclude.Include.NON_NULL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,14 @@ public class RestControllerHelper<T> {

public User getSw360UserFromAuthentication() {
try {
String userId = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String userId;
Object principle = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principle instanceof String) {
userId = principle.toString();
} else {
org.springframework.security.core.userdetails.User user = (org.springframework.security.core.userdetails.User) principle;
userId = user.getUsername();
}
return userService.getUserByEmailOrExternalId(userId);
} catch (RuntimeException e) {
throw new AuthenticationServiceException("Could not load user from authentication.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
SPDX-FileCopyrightText: © 2022 Siemens AG
SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.sw360.rest.resourceserver.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
public class EndpointsFilter extends OncePerRequestFilter {

private static final String ERROR_MESSAGE = "Service is disabled";

private static final String CREATE_USER_ENDPOINT = "/resource/api/users";

@Value("${sw360.rest.api.createuser.disabled}")
boolean disabledUsrCreation;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestURI = request.getRequestURI();
String method = request.getMethod();

if (disabledUsrCreation && requestURI.equalsIgnoreCase(CREATE_USER_ENDPOINT) && method.equals("POST")) {
response.sendError(HttpStatus.SERVICE_UNAVAILABLE.value(), ERROR_MESSAGE);
} else {
filterChain.doFilter(request, response);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@

package org.eclipse.sw360.rest.resourceserver.security;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.sw360.rest.resourceserver.core.SimpleAuthenticationEntryPoint;
import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationFilter;
import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationProvider;
import org.eclipse.sw360.rest.resourceserver.security.basic.Sw360CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpMethod;
Expand All @@ -24,10 +28,12 @@
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

@Profile("!SECURITY_MOCK")
Expand All @@ -37,12 +43,17 @@
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements ResourceServerConfigurer {

private final Logger log = LogManager.getLogger(this.getClass());

@Autowired
private ApiTokenAuthenticationFilter filter;

@Autowired
private ApiTokenAuthenticationProvider authProvider;

@Autowired
private Sw360CustomUserDetailsService userDetailsService;

@Autowired
private ResourceServerProperties resourceServerProperties;

Expand All @@ -53,6 +64,11 @@ public ResourceServerConfiguration(ResourceServerProperties resourceServerProper
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(this.authProvider);
try {
authenticationManagerBuilder.userDetailsService(userDetailsService);
} catch (Exception e) {
log.error("Error in Authentication", e);
}
}

@Override
Expand All @@ -73,6 +89,7 @@ public void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(filter, BasicAuthenticationFilter.class)
.authenticationProvider(authProvider)
.userDetailsService(userDetailsService)
.httpBasic()
.and()
.authorizeRequests()
Expand All @@ -86,4 +103,9 @@ public void configure(HttpSecurity http) throws Exception {
.antMatchers(HttpMethod.PATCH, "/api/**").hasAuthority("WRITE").and()
.csrf().disable().exceptionHandling().authenticationEntryPoint(saep);
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: © 2022 Siemens AG
SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.sw360.rest.resourceserver.security.basic;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.sw360.datahandler.thrift.users.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;

@Profile("!SECURITY_MOCK")
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Sw360CustomUserDetailsService implements UserDetailsService {

private static final Logger log = LogManager.getLogger(Sw360CustomUserDetailsService.class);

@Autowired
Sw360UserDetailsProvider sw360UserDetailsProvider;

@Override
public UserDetails loadUserByUsername(String userid) {
log.info("Authenticating for the user with username {}", userid);
User user = sw360UserDetailsProvider.provideUserDetails(userid, null);
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(),
Sw360GrantedAuthoritiesCalculator.generateFromUser(user));
}
}
Loading