Skip to content

Commit

Permalink
Merge pull request #430 from peissing/main
Browse files Browse the repository at this point in the history
Rebase and added all changes for Cognito support
  • Loading branch information
tunacicek authored Jun 24, 2024
2 parents 6b63ff3 + e56ccf9 commit 945bf1f
Show file tree
Hide file tree
Showing 17 changed files with 489 additions and 45 deletions.
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ The following people have contributed to this repository:
* Aggarwal Sahil, Robert Bosch GmbH, [email protected]
* Tunahan Cicek, Robert Bosch GmbH, [email protected]
* Istvan Zoltan Nagy, Robert Bosch GmbH, [email protected]
* Anton Peissinger, Draexlmaier Group, [email protected]

Please add yourself to this list, if you contribute to the content.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## 0.5.0-RC1
### Added

- AWS Cognito support for authentication
## fixed
- Bugfix access rule management for submodelfilter (visibleSemanticIds): Remove condition on `semanticId.keys.type = submodel` because the `semanticId.keys.type` should be `GlobalReference`. This check is not needed.
- Implemented mandatory changes in licensing and legal documentation
Expand Down
2 changes: 2 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ The Helm Chart can be configured using the following parameters (incomplete list
| `registry.ingress.className` | The `Ingress` class name | `nginx` |
| `registry.ingress.annotations` | Annotations to further configure the `Ingress` resource, e.g. for using with `cert-manager`. | |
| `registry.tenantId` | TenantId which is the owner of the DTR. | |
| `registry.identityProvider` | Identity provider for the DTR. Possible values are `keycloak` or `cognito`. | |
| `registry.idpInternalClientId` | The client id for the app client in Cognito that as full access to the DTR. | |
| `registry.externalSubjectIdWildcardPrefix` | WildcardPrefix to make a specificAssetId visible for everyone. | `PUBLIC_READABLE` |
| `registry.externalSubjectIdWildcardAllowedTypes` | List of allowed types that can be made visible to everyone. | `manufacturerPartId,assetLifecyclePhase` |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@

import java.util.List;

import lombok.Data;
import org.eclipse.tractusx.semantics.registry.security.OAuthSecurityConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;


@Data
Expand Down Expand Up @@ -64,13 +65,25 @@ public class RegistryProperties {
@Data
@NotNull
public static class Idm {
/**
/**
* The identityProvider that should be used. Allowed are keycloak or cognito
*/
@NotEmpty(message = "identityProvider must not be empty. Allowed is keycloak or cognito")
private String identityProvider = OAuthSecurityConfig.IdentityProvider.KEYCLOAK.name().toLowerCase();

/**
* The public client id used for the redirect urls.
*/
@NotEmpty(message = "public client id must not be empty")
private String publicClientId;

/**
/**
* The internal client id used for writing operations to registry. Used if
* identity provider is cognito
*/
private String internalClientId;

/**
* The owning tenant id to which this AAS Registry belongs to.
*/
@NotEmpty(message = "owningTenantId must not be empty")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*******************************************************************************
* Copyright (c) 2021 Robert Bosch Manufacturing Solutions GmbH and others
* Copyright (c) 2021 Contributors to the Eclipse Foundation
* Copyright (c) 2024 Contributors to the Eclipse Foundation
* Copyright (c) 2024 Draexlmaier Group
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
Expand All @@ -22,15 +23,6 @@

import static org.eclipse.tractusx.semantics.registry.security.AuthorizationEvaluator.Roles.*;

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

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

import lombok.extern.slf4j.Slf4j;

/**
* This class contains methods validating JWT tokens for correctness and ensuring that the JWT token contains a desired role.
* The methods are meant to be used in Spring Security expressions for RBAC on API operations.
Expand All @@ -48,12 +40,11 @@
* the token will be considered invalid. Invalid tokens result in 403.
*
*/
@Slf4j
public class AuthorizationEvaluator {
public abstract class AuthorizationEvaluator {

private final String clientId;

public AuthorizationEvaluator( String clientId ) {
protected AuthorizationEvaluator( String clientId ) {
this.clientId = clientId;
}

Expand Down Expand Up @@ -85,34 +76,16 @@ public boolean hasRoleWriteAccessRules() {
return containsRole( ROLE_WRITE_ACCESS_RULES );
}

private boolean containsRole( String role ) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if ( !(authentication instanceof JwtAuthenticationToken) ) {
return false;
}

JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) (authentication);
Map<String, Object> claims = jwtAuthenticationToken.getToken().getClaims();

Object resourceAccess = claims.get( "resource_access" );
if ( !(resourceAccess instanceof Map) ) {
return false;
}
protected abstract boolean containsRole( String role ) ;

Object resource = ((Map<String, Object>) resourceAccess).get( clientId );
if ( !(resource instanceof Map) ) {
return false;
}

Object roles = ((Map<String, Object>) resource).get( "roles" );
if ( !(roles instanceof Collection) ) {
return false;
}

Collection<String> rolesList = (Collection<String>) roles;
return rolesList.contains( role );
/**
* get the client id
* @return the client id
*/
protected String getClientId() {
return clientId;
}

/**
* Represents the roles defined for the registry.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*******************************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
* Copyright (c) 2024 Draexlmaier Group
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/

package org.eclipse.tractusx.semantics.registry.security;

import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.tractusx.semantics.RegistryProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

/**
* Conito Authenticator We have two client_ids This is necessary because we have
* two clients, one for reading data and one for full access
*/
public final class CognitoAuthorizationEvaluator extends AuthorizationEvaluator {
private static final Logger log = LoggerFactory.getLogger(CognitoAuthorizationEvaluator.class);

private final String internalClientId;

public CognitoAuthorizationEvaluator(final RegistryProperties.Idm idm) {
super(idm.getPublicClientId());
this.internalClientId = idm.getInternalClientId();
}

@Override
protected boolean containsRole(final String role) {
CognitoAuthorizationEvaluator.log.debug("Checking if token contains role {}", role);
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!(authentication instanceof JwtAuthenticationToken)) {
return false;
}

final JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) (authentication);
final Map<String, Object> claims = jwtAuthenticationToken.getToken().getClaims();

final Object claimClientId = claims.get("client_id");

if (StringUtils.equals(this.internalClientId, (String) claimClientId) || StringUtils.equals(this.getClientId(), (String) claimClientId)) {
final Object scope = claims.get("scope");
if (scope instanceof final String scopeString) {
final String[] split = StringUtils.split(scopeString, ' ');
for (final String tokenRole : split) {
if (StringUtils.contains(tokenRole, role)) {
CognitoAuthorizationEvaluator.log.debug("Role {} found in token", role);
return true;
}
}
}
}
CognitoAuthorizationEvaluator.log.debug("Role {} NOT found in token", role);
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
* Copyright (c) 2024 Draexlmaier Group
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/

package org.eclipse.tractusx.semantics.registry.security;

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

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

/**
* Keycloak Authorization Evaluator
*/
public final class KeycloakAuthorizationEvaluator extends AuthorizationEvaluator {

/**
* Constructor
* @param clientId clientId
*/
public KeycloakAuthorizationEvaluator(String clientId) {
super(clientId);
}

protected boolean containsRole( String role ) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if ( !(authentication instanceof JwtAuthenticationToken) ) {
return false;
}

JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) (authentication);
Map<String, Object> claims = jwtAuthenticationToken.getToken().getClaims();

Object resourceAccess = claims.get( "resource_access" );
if ( !(resourceAccess instanceof Map) ) {
return false;
}

Object resource = ((Map<String, Object>) resourceAccess).get( this.getClientId() );
if ( !(resource instanceof Map) ) {
return false;
}

Object roles = ((Map<String, Object>) resource).get( "roles" );
if ( !(roles instanceof Collection) ) {
return false;
}

Collection<String> rolesList = (Collection<String>) roles;
return rolesList.contains( role );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,45 @@ protected SecurityFilterChain configure(HttpSecurity http) throws Exception {

@Bean
public AuthorizationEvaluator authorizationEvaluator(RegistryProperties registryProperties){
return new AuthorizationEvaluator(registryProperties.getIdm().getPublicClientId());
final IdentityProvider provider = IdentityProvider.getIdentityProvider(registryProperties.getIdm().getIdentityProvider());
return provider.createAuthorizationEvaluator(registryProperties.getIdm());
}

/**
* enum containing the possible identity providers for the DTR
*/
public enum IdentityProvider {

KEYCLOAK {
@Override
public AuthorizationEvaluator createAuthorizationEvaluator(RegistryProperties.Idm idmProperties) {
return new KeycloakAuthorizationEvaluator(idmProperties.getPublicClientId());
}
},

COGNITO {
@Override
public AuthorizationEvaluator createAuthorizationEvaluator(RegistryProperties.Idm idmProperties) {
return new CognitoAuthorizationEvaluator(idmProperties);
}
};

public abstract AuthorizationEvaluator createAuthorizationEvaluator(RegistryProperties.Idm idmProperties);

/**
* tries to find the Identity Provider enum element based on a string, ignores
* case!.
*
* @param key the key to search
* @return the key as enum
*/

public static IdentityProvider getIdentityProvider(final String identityProviderName) {
try {
return IdentityProvider.valueOf(identityProviderName.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Unknown identityProvider: " + identityProviderName, e);
}
}
}
}
Loading

0 comments on commit 945bf1f

Please sign in to comment.