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.
Expand Up @@ -25,6 +25,7 @@
* @author cedric.bodet@tngtech.com
* @author stefan.jaeger@evosoft.com
* @author alex.borodin@evosoft.com
* @author thomas.maier@evosoft.com
*/
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
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
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
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;
}
}
}
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
@@ -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
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

0 comments on commit 5ff8423

Please sign in to comment.