Skip to content
This repository has been archived by the owner on Jul 25, 2018. It is now read-only.

Commit

Permalink
feat(rest): add admin privilege verification to the authorization server
Browse files Browse the repository at this point in the history
  • Loading branch information
maierthomas committed Dec 22, 2017
1 parent 4b5098b commit 5ff8423
Show file tree
Hide file tree
Showing 33 changed files with 514 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* @author [email protected]
* @author [email protected]
* @author [email protected]
* @author [email protected]
*/
public class PermissionUtils {

Expand All @@ -33,7 +34,7 @@ public static boolean isNormalUser(User user) {
}

public static boolean isAdmin(User user) {
return isInGroup(user,UserGroup.SW360_ADMIN) || isInGroup(user, UserGroup.ADMIN);
return isInGroup(user, UserGroup.SW360_ADMIN) || isInGroup(user, UserGroup.ADMIN);
}

public static boolean isClearingAdmin(User user) {
Expand All @@ -55,13 +56,15 @@ private static boolean isInGroup(User user, UserGroup userGroup) {
public static boolean isUserAtLeast(UserGroup group, User user) {
switch (group) {
case USER:
return isNormalUser(user) || isClearingAdmin(user) || isEccAdmin(user) || isAdmin(user) || isSecurityAdmin(user);
return isNormalUser(user) || isAdmin(user) || isClearingAdmin(user) || isEccAdmin(user) || isSecurityAdmin(user);
case CLEARING_ADMIN:
return isClearingAdmin(user) || isAdmin(user);
case ECC_ADMIN:
return isEccAdmin(user) || isAdmin(user);
case SECURITY_ADMIN:
return isSecurityAdmin(user) || isAdmin(user);
return isSecurityAdmin(user) || isAdmin(user);
case SW360_ADMIN:
return isAdmin(user);
case ADMIN:
return isAdmin(user);
default:
Expand Down
8 changes: 7 additions & 1 deletion rest/authorization-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
</parent>

<artifactId>authorization-server</artifactId>

<packaging>war</packaging>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -112,6 +112,12 @@
<version>${project-lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.sw360</groupId>
<artifactId>datahandler</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,34 @@

package org.eclipse.sw360.rest.authserver;

import org.eclipse.sw360.datahandler.common.CommonUtils;
import org.eclipse.sw360.datahandler.thrift.users.UserGroup;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

import java.util.Properties;

@SpringBootApplication
public class Sw360AuthorizationServer extends SpringBootServletInitializer {

private static final String PROPERTIES_FILE_PATH = "/sw360.properties";
private static final String DEFAULT_ACCESS_TIME_IN_SECONDS = "3600";
private static final String DEFAULT_WRITE_ACCESS_USERGROUP = UserGroup.SW360_ADMIN.name();

public static final int TOKEN_ACCESS_VALIDITY;
public static final UserGroup WRITE_ACCESS_USERGROUP;

static {
Properties props = CommonUtils.loadProperties(Sw360AuthorizationServer.class, PROPERTIES_FILE_PATH);

TOKEN_ACCESS_VALIDITY = Integer.parseInt(props.getProperty(
"rest.token.access.validity", DEFAULT_ACCESS_TIME_IN_SECONDS));
WRITE_ACCESS_USERGROUP = UserGroup.valueOf(props.getProperty(
"rest.write.access.usergroup", DEFAULT_WRITE_ACCESS_USERGROUP));
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Sw360AuthorizationServer.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@

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

import com.google.common.base.Strings;
import org.apache.thrift.TException;
import org.eclipse.sw360.datahandler.permissions.PermissionUtils;
import org.eclipse.sw360.datahandler.thrift.ThriftClients;
import org.eclipse.sw360.datahandler.thrift.users.User;
import org.eclipse.sw360.datahandler.thrift.users.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
Expand All @@ -29,9 +35,15 @@
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static org.eclipse.sw360.rest.authserver.Sw360AuthorizationServer.WRITE_ACCESS_USERGROUP;
import static org.eclipse.sw360.rest.authserver.security.Sw360GrantedAuthority.READ;
import static org.eclipse.sw360.rest.authserver.security.Sw360GrantedAuthority.WRITE;

@Component
public class Sw360AuthenticationProvider implements AuthenticationProvider {

@Value("${sw360.test-user-id:#{null}}")
private String testUserId;

Expand All @@ -47,59 +59,100 @@ public class Sw360AuthenticationProvider implements AuthenticationProvider {
@Autowired
Environment environment;

private static final String ENVIRONMENT_DEV_PROFILE = "dev";

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName(); // this must be an email
String email = authentication.getName();
String password = authentication.getCredentials().toString();

boolean isDev = environment.getActiveProfiles().length == 1 && environment.getActiveProfiles()[0].equals("dev");

if (isDev && testUserId != null && testUserPassword != null) {
// For easy testing without having a LifeRay portal running,
// we mock an existing sw360 user
if (name.equals(testUserId) && password.equals(testUserPassword)) {
return createAuthenticationToken(name, password);
if (isDevEnvironment() && testUserId != null && testUserPassword != null) {
// For easy testing without having a Liferay portal running, we mock an existing sw360 user
if (email.equals(testUserId) && password.equals(testUserPassword)) {
return createAuthenticationToken(email, password, null);
}
} else if (isValidString(sw360PortalServerURL) && isValidString(sw360LiferayCompanyId)) {
String url = sw360PortalServerURL +
String.format("/api/jsonws/user/get-user-id-by-email-address?companyId=%s&emailAddress=%s",
sw360LiferayCompanyId, name);
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
String encodedPassword = null;
try {
encodedPassword = URLDecoder.decode(password, "US-ASCII");
} catch (UnsupportedEncodingException e) {
return null;
// Verify if the user exists in sw360 and set the corresponding authority (read, write)
if (isAuthorized(email, password)) {
User user = getUserByEmail(email);
if (!Objects.isNull(user)) {
return createAuthenticationToken(email, password, user);
}
}
RestTemplate restTemplate = restTemplateBuilder.basicAuthorization(
name, encodedPassword).build();
ResponseEntity<String> response = restTemplate.postForEntity(
url, null, String.class);
String userId = response.getBody();

// if this is a number, everything is ok
try {
Integer.parseInt(userId);
} catch (NumberFormatException e) {
return null;
}
return createAuthenticationToken(name, password);
}
return null;
}

private Authentication createAuthenticationToken(String name, String password) {
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}

private boolean isAuthorized(String email, String password) {
// Solution 1:
// UserLocalServiceUtil.authenticateForBasic
// userId = UserLocalServiceUtil.authenticateForBasic(companyId, authType, login, current);
// this need a dependency to liferay

// Solution 2:
// Liferay json webservice call to verify username and password
String liferayParameterURL = "/api/jsonws/user/get-user-id-by-email-address?companyId=%s&emailAddress=%s";
String url = sw360PortalServerURL + String.format(liferayParameterURL, sw360LiferayCompanyId, email);
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
String encodedPassword;
try {
encodedPassword = URLDecoder.decode(password, "US-ASCII");
} catch (UnsupportedEncodingException e) {
return false;
}
RestTemplate restTemplate = restTemplateBuilder.basicAuthorization(email, encodedPassword).build();
ResponseEntity<String> response = restTemplate.postForEntity(url, null, String.class);
return (parseInteger(response.getBody()) > 0);
}

private User getUserByEmail(String email) {
UserService.Iface client = new ThriftClients().makeUserClient();
User user = null;
try {
if (!Strings.isNullOrEmpty(email) && client != null) {
user = client.getByEmail(email);
}
} catch (TException e) {
user = null;
}
return user;
}

private Authentication createAuthenticationToken(String name, String password, User user) {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_SW360_USER"));
grantedAuthorities.add(new SimpleGrantedAuthority(READ.getAuthority()));
if (!Objects.isNull(user) && PermissionUtils.isUserAtLeast(WRITE_ACCESS_USERGROUP, user)) {
grantedAuthorities.add(new SimpleGrantedAuthority(WRITE.getAuthority()));
}
return new UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
}

@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
private boolean isDevEnvironment() {
Boolean result = false;
String[] activeProfiles = environment.getActiveProfiles();
for (String profile : activeProfiles) {
if (profile.equals(ENVIRONMENT_DEV_PROFILE)) {
result = true;
break;
}
}
return result;
}

private int parseInteger(String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return 0;
}
}

private boolean isValidString(String string) {
return string != null && string.trim().length() != 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
Expand All @@ -27,12 +28,30 @@
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import static org.eclipse.sw360.rest.authserver.Sw360AuthorizationServer.TOKEN_ACCESS_VALIDITY;
import static org.eclipse.sw360.rest.authserver.security.Sw360GrantedAuthority.BASIC;

@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Sw360AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;

@Value("${security.oauth2.client.client-id}")
private String clientId;

@Value("${security.oauth2.client.authorized-grant-types}")
private String[] authorizedGrantTypes;

@Value("${security.oauth2.client.resource-ids}")
private String resourceIds;

@Value("${security.oauth2.client.scope}")
private String[] scopes;

@Value("${security.oauth2.client.client-secret}")
private String clientSecret;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
Expand All @@ -43,22 +62,21 @@ public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws E

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_SW360_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_SW360_CLIENT')");
String serverAuthority = BASIC.getAuthority();
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('" + serverAuthority + "')")
.checkTokenAccess("hasAuthority('" + serverAuthority + "')");
}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// TODO Kai Tödter 2016-12-04
// Externalize those config parameters in the application.yml
clients.inMemory()
.withClient("trusted-sw360-client")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_TRUSTED_SW360_CLIENT")
.scopes("sw360.read", "sw360.write")
.resourceIds("sw360-REST-API")
.accessTokenValiditySeconds(3600)
.secret("sw360-secret");
.withClient(clientId)
.authorizedGrantTypes(authorizedGrantTypes)
.authorities(BASIC.getAuthority())
.scopes(scopes)
.resourceIds(resourceIds)
.accessTokenValiditySeconds(TOKEN_ACCESS_VALIDITY)
.secret(clientSecret);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Siemens AG, 2017. Part of the SW360 Portal Project.
*
* SPDX-License-Identifier: EPL-1.0
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/

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

import org.springframework.security.core.GrantedAuthority;

public enum Sw360GrantedAuthority implements GrantedAuthority {

/*
* BASIC:
* only authorized with clientName/clientSecret secret (without valid sw360 user credentials)
*/
BASIC,

/*
* READ:
* authorized with clientName/clientSecret and valid sw360 user (without rest api write privileges)
*/
READ,

/*
* WRITE
* authorized with clientName/clientSecret and valid sw360 user with rest api write privileges
*/
WRITE;

@Override
public String getAuthority() {
return toString();
}

}
7 changes: 2 additions & 5 deletions rest/authorization-server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ sw360:
sw360-portal-server-url: ${SW360_PORTAL_SERVER_URL:http://127.0.0.1:8080}
sw360-liferay-company-id: ${SW360_LIFERAY_COMPANY_ID:20155}

# currently not used, hardcoded in Sw360AuthorizationServerConfiguration
security:
oauth2:
resource:
Expand All @@ -23,7 +22,5 @@ security:
client-id: trusted-sw360-client
client-secret: sw360-secret
resource-ids: sw360-REST-API
authorized-grant-types: client_credentials password
authorities: ROLE_TRUSTED_SW360_CLIENT
access-token-validity-seconds: 360
scope: sw360.read sw360.write
authorized-grant-types: client_credentials,password
scope: all
Loading

0 comments on commit 5ff8423

Please sign in to comment.