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

Rebase and added all changes for Cognito support #430

Merged
merged 4 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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,6 @@
/*******************************************************************************
* Copyright (c) 2021 Robert Bosch Manufacturing Solutions GmbH and others
tunacicek marked this conversation as resolved.
Show resolved Hide resolved
* 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 +22,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 +39,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 +75,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
Loading