Skip to content

Commit

Permalink
feat(client-management): added support to dynamically manage oauth cl…
Browse files Browse the repository at this point in the history
…ients

* added sso authentication support to standard requests
* added liferay authentication support to standard requests
* added REST service for basic oauth client management
* added couchdb persistence for oauth client management
* removed single static configurable oauth client
* added refresh_token workflow to all configured clients
* added some tests and javadoc

For SSO users (basic auth liferay users can use other tools as well, but
can also use this workflow):
1. open a browser with developer tools
2. open
    https://<sw360domain>/authorization/client-management
3. to add a new client, enter the following javascript in the dev tools
console
    xmlHttpRequest = new XMLHttpRequest();
    xmlHttpRequest.open('POST', '/authorization/client-management', false);
    xmlHttpRequest.setRequestHeader('Content-Type', 'application/json');
    xmlHttpRequest.setRequestHeader('Accept', 'application/json');
    xmlHttpRequest.send(JSON.stringify(
      {
        "description" : "my first test client",
        "authorities" : [ "BASIC" ],
        "scope" : [ "READ" ],
        "access_token_validity" : 3600,
        "refresh_token_validity" : 3600
      }
    ));
    console.log(xmlHttpRequest.responseText);
4. to manipulate an existing client, do the same but add the clientid to
the data object
        "client_id" : "9e358ca832ce4ce99a770c7bd0f8e066"
5. to remove an existing client, enter the following javascript in the
dev tools console
    xmlHttpRequest = new XMLHttpRequest();
    xmlHttpRequest.open('DELETE', '/authorization/client-management/9e358ca832ce4ce99a770c7bd0f8e066', false);
    xmlHttpRequest.setRequestHeader('Content-Type', 'application/json');
    xmlHttpRequest.setRequestHeader('Accept', 'application/json');
    xmlHttpRequest.send();
    console.log(xmlHttpRequest.responseText);
This way the session cookie of the SSO login will be used for the REST
calls. This might also be possible in postman or curl or similar tools if
you want to try to copy cookies (depending also on the SSO configuration).

closes eclipse-sw360#543

Signed-off-by: Andreas Klett <[email protected]>
  • Loading branch information
imaykay authored and maxhbr committed Jul 24, 2019
1 parent b21bedd commit 375c61e
Show file tree
Hide file tree
Showing 28 changed files with 1,346 additions and 294 deletions.
16 changes: 16 additions & 0 deletions rest/authorization-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-core</artifactId>
Expand All @@ -121,6 +126,17 @@
<artifactId>rest-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.ektorp</groupId>
<artifactId>org.ektorp</artifactId>
<version>${ektorp.version}</version>
<exclusions>
<exclusion>
<groupId>net.sourceforge.findbugs</groupId>
<artifactId>annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@

package org.eclipse.sw360.rest.authserver;

import org.apache.log4j.Logger;
import org.eclipse.sw360.datahandler.common.CommonUtils;
import org.eclipse.sw360.datahandler.thrift.users.UserGroup;
import org.eclipse.sw360.rest.common.Sw360CORSFilter;

import org.apache.log4j.Logger;
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 org.springframework.context.annotation.Import;

import javax.crypto.SealedObject;

import java.io.IOException;
import java.util.Properties;

Expand All @@ -35,18 +37,17 @@ public class Sw360AuthorizationServer extends SpringBootServletInitializer {

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

public static final String CONFIG_ACCESS_TOKEN_VALIDITY_SECONDS;
public static final UserGroup CONFIG_WRITE_ACCESS_USERGROUP;
public static final String CONFIG_CLIENT_ID;
public static final SealedObject CONFIG_CLIENT_SECRET;
public static final UserGroup CONFIG_ADMIN_ACCESS_USERGROUP;

static {
Properties props = CommonUtils.loadProperties(Sw360AuthorizationServer.class, PROPERTIES_FILE_PATH);
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));
CONFIG_ACCESS_TOKEN_VALIDITY_SECONDS = props.getProperty("rest.access.token.validity.seconds", null);
CONFIG_CLIENT_ID = props.getProperty("rest.security.client.id", null);
CONFIG_CLIENT_SECRET = getConfigClientSecret(props);
}

private static SealedObject getConfigClientSecret(Properties props) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Copyright Siemens AG, 2019. 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.client.persistence;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import org.ektorp.support.CouchDbDocument;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.Jackson2ArrayOrStringDeserializer;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

public class OAuthClientEntity extends CouchDbDocument implements ClientDetails {

private BaseClientDetails delegate;

private String description;

public OAuthClientEntity() {
this.delegate = new BaseClientDetails();
}

@Override
@JsonProperty("client_id")
public String getClientId() {
return delegate.getClientId();
}

@JsonProperty("client_id")
public void setClientId(String clientId) {
delegate.setClientId(clientId);
}

@Override
@JsonProperty("client_secret")
public String getClientSecret() {
return delegate.getClientSecret();
}

@JsonProperty("client_secret")
public void setClientSecret(String clientSecret) {
delegate.setClientSecret(clientSecret);
}

@JsonProperty("description")
public String getDescription() {
return description;
}

@JsonProperty("description")
public void setDescription(String description) {
this.description = description;
}

@Override
@JsonProperty("resource_ids")
@JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class)
public Set<String> getResourceIds() {
return delegate.getResourceIds();
}

@JsonProperty("resource_ids")
public void setResourceIds(Set<String> clientSecret) {
delegate.setResourceIds(clientSecret);
}

@Override
@JsonProperty("authorized_grant_types")
@JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class)
public Set<String> getAuthorizedGrantTypes() {
return delegate.getAuthorizedGrantTypes();
}

@JsonProperty("authorized_grant_types")
public void setAuthorizedGrantTypes(Set<String> authorizedGrantTypes) {
delegate.setAuthorizedGrantTypes(authorizedGrantTypes);
}

@Override
public Collection<GrantedAuthority> getAuthorities() {
return delegate.getAuthorities();
}

public void setAuthorities(Collection<GrantedAuthority> authorities) {
delegate.setAuthorities(authorities);
}

@JsonProperty("authorities")
public Set<String> getAuthoritiesAsStrings() {
return AuthorityUtils.authorityListToSet(delegate.getAuthorities());
}

@JsonProperty("authorities")
@JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class)
public void setAuthoritiesAsStrings(Set<String> values) {
delegate.setAuthorities(AuthorityUtils.createAuthorityList(values.toArray(new String[values.size()])));
}

@Override
@JsonProperty("scope")
@JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class)
public Set<String> getScope() {
return delegate.getScope();
}

@JsonProperty("scope")
public void setScope(Set<String> scope) {
delegate.setScope(scope);
}

@Override
@JsonProperty("redirect_uri")
@JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class)
public Set<String> getRegisteredRedirectUri() {
return delegate.getRegisteredRedirectUri();
}

@JsonProperty("redirect_uri")
public void setRegisteredRedirectUri(Set<String> registeredRedirectUri) {
delegate.setRegisteredRedirectUri(registeredRedirectUri);
}

@Override
@JsonProperty("access_token_validity")
public Integer getAccessTokenValiditySeconds() {
return delegate.getAccessTokenValiditySeconds();
}

@JsonProperty("access_token_validity")
public void setAccessTokenValiditySeconds(Integer accessTokenValiditySeconds) {
delegate.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
}

@Override
@JsonProperty("refresh_token_validity")
public Integer getRefreshTokenValiditySeconds() {
return delegate.getRefreshTokenValiditySeconds();
}

@JsonProperty("refresh_token_validity")
public void setRefreshTokenValiditySeconds(Integer refreshTokenValiditySeconds) {
delegate.setRefreshTokenValiditySeconds(refreshTokenValiditySeconds);
}

@JsonProperty("autoapprove")
public Set<String> getAutoApproveScopes() {
return delegate.getAutoApproveScopes();
}

@JsonProperty("autoapprove")
public void setAutoApproveScopes(Set<String> autoApproveScopes) {
delegate.setAutoApproveScopes(autoApproveScopes);
}

@Override
@JsonAnyGetter
public Map<String, Object> getAdditionalInformation() {
return delegate.getAdditionalInformation();
}

@JsonAnySetter
public void addAdditionalInformation(String key, Object value) {
delegate.addAdditionalInformation(key, value);
}

@Override
public boolean isSecretRequired() {
return delegate.isSecretRequired();
}

@Override
public boolean isScoped() {
return delegate.isScoped();
}

@Override
public boolean isAutoApprove(String scope) {
return delegate.isAutoApprove(scope);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright Siemens AG, 2019. 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.client.persistence;

import org.ektorp.ViewQuery;
import org.ektorp.http.StdHttpClient;
import org.ektorp.impl.StdCouchDbConnector;
import org.ektorp.impl.StdCouchDbInstance;
import org.ektorp.support.CouchDbRepositorySupport;
import org.ektorp.support.View;
import org.ektorp.support.Views;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.net.MalformedURLException;
import java.util.List;

/**
* This repository can perform CRUD operations on a CouchDB for
* {@link OAuthClientEntity}s. The necessary configuration for the CouchDB
* connection has to be available to Spring's {@link Value} infrastructure.
*/
@Component
@Views({
@View(name = "all", map = "function(doc) { emit(null, doc._id); }"),
@View(name = "byId", map = "function(doc) { emit(doc._id, doc); }"),
@View(name = "byClientId", map = "function(doc) { emit(doc.client_id, doc); }")
})
public class OAuthClientRepository extends CouchDbRepositorySupport<OAuthClientEntity> {

protected OAuthClientRepository(
@Value("${couchdb.url}") final String dbUrl,
@Value("${couchdb.database}") final String dbName,
@Value("${couchdb.username:#{null}}") final String dbUsername,
@Value("${couchdb.password:#{null}}") final String dbPassword) throws MalformedURLException {

super(OAuthClientEntity.class, new StdCouchDbConnector(dbName,
new StdCouchDbInstance(
new StdHttpClient.Builder()
.url(dbUrl)
.username(dbUsername)
.password(dbPassword)
.build()
)
)
);

initStandardDesignDocument();
}

public OAuthClientEntity getByClientId(String clientId) {
ViewQuery query = createQuery("byClientId");
query.setIgnoreNotFound(true);
query.key(clientId);

List<OAuthClientEntity> clients = db.queryView(query, OAuthClientEntity.class);
if (clients.size() < 1) {
log.warn("No clients found for clientId <{}>.", clientId);
return null;
} else if (clients.size() > 1) {
log.warn("More than one client found ({}) for clientId <{}>.", clients.size(), clientId);
return clients.get(0);
} else {
log.debug("Client found for clientId <{}>", clientId);
return clients.get(0);
}
}

}
Loading

0 comments on commit 375c61e

Please sign in to comment.