Skip to content

Commit

Permalink
feat: IATP: STS core services
Browse files Browse the repository at this point in the history
  • Loading branch information
wolf4ood committed Oct 19, 2023
1 parent 027a961 commit da1fb7a
Show file tree
Hide file tree
Showing 25 changed files with 1,279 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.jwt;

import org.eclipse.edc.jwt.spi.JwtDecorator;
import org.eclipse.edc.jwt.spi.TokenGenerationService;
import org.eclipse.edc.spi.iam.TokenRepresentation;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.security.PrivateKeyResolver;
import org.jetbrains.annotations.NotNull;

import java.security.PrivateKey;
import java.util.Objects;

/**
* Token generator that wraps {@link TokenGenerationServiceImpl} and does not cache the private
* key, but instead it resolves it at token generation time.
*/
public class LazyTokenGenerationService implements TokenGenerationService {

private final PrivateKeyResolver privateKeyResolver;
private final String keyAlias;

public LazyTokenGenerationService(PrivateKeyResolver privateKeyResolver, String keyAlias) {
this.privateKeyResolver = Objects.requireNonNull(privateKeyResolver);
this.keyAlias = Objects.requireNonNull(keyAlias);
}

@Override
public Result<TokenRepresentation> generate(@NotNull JwtDecorator... decorators) {
var key = privateKeyResolver.resolvePrivateKey(keyAlias, PrivateKey.class);
return new TokenGenerationServiceImpl(key).generate(decorators);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
`java-library`
`maven-publish`
}

dependencies {
api(project(":spi:common:transaction-spi"))
api(project(":spi:common:identity-trust-spi"))
api(project(":spi:common:identity-trust-sts-spi"))
implementation(project(":extensions:common:iam:identity-trust:identity-trust-sts-embedded"))
implementation(project(":core:common:jwt-core"))

testImplementation(testFixtures(project(":spi:common:identity-trust-sts-spi")))
testImplementation(project(":core:common:junit"))
testImplementation(libs.nimbus.jwt)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.iam.identitytrust.sts.core;

import org.eclipse.edc.iam.identitytrust.sts.core.defaults.service.StsClientServiceImpl;
import org.eclipse.edc.iam.identitytrust.sts.core.defaults.service.StsClientTokenGeneratorServiceImpl;
import org.eclipse.edc.iam.identitytrust.sts.service.StsClientService;
import org.eclipse.edc.iam.identitytrust.sts.service.StsClientTokenGeneratorService;
import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore;
import org.eclipse.edc.jwt.LazyTokenGenerationService;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.runtime.metamodel.annotation.Setting;
import org.eclipse.edc.spi.security.PrivateKeyResolver;
import org.eclipse.edc.spi.security.Vault;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.transaction.spi.TransactionContext;

import java.time.Clock;
import java.util.concurrent.TimeUnit;

@Extension(StsDefaultServicesExtension.NAME)
public class StsDefaultServicesExtension implements ServiceExtension {

public static final String NAME = "Secure Token Service Default Services";

@Setting(value = "Self-issued ID Token expiration in minutes. By default is 5 minutes", defaultValue = "" + StsDefaultServicesExtension.DEFAULT_STS_TOKEN_EXPIRATION_MIN)
private static final String STS_TOKEN_EXPIRATION = "edc.iam.sts.token.expiration"; // in minutes

private static final int DEFAULT_STS_TOKEN_EXPIRATION_MIN = 5;

@Inject
private StsClientStore clientStore;

@Inject
private TransactionContext transactionContext;

@Inject
private Vault vault;

@Inject
private PrivateKeyResolver privateKeyResolver;

@Inject
private Clock clock;

@Override
public String name() {
return NAME;
}

@Provider
public StsClientTokenGeneratorService clientTokenService(ServiceExtensionContext context) {
var tokenExpiration = context.getSetting(STS_TOKEN_EXPIRATION, DEFAULT_STS_TOKEN_EXPIRATION_MIN);
return new StsClientTokenGeneratorServiceImpl(
(client) -> new LazyTokenGenerationService(privateKeyResolver, client.getPrivateKeyAlias()),
clock,
TimeUnit.MINUTES.toSeconds(tokenExpiration));
}

@Provider
public StsClientService clientService() {
return new StsClientServiceImpl(clientStore, vault, transactionContext);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.iam.identitytrust.sts.core;

import org.eclipse.edc.iam.identitytrust.sts.core.defaults.store.InMemoryStsClientStore;
import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.spi.system.ServiceExtension;

@Extension(StsDefaultStoresExtension.NAME)
public class StsDefaultStoresExtension implements ServiceExtension {

public static final String NAME = "Secure Token Service Default Stores";

@Override
public String name() {
return NAME;
}

@Provider(isDefault = true)
public StsClientStore clientStore() {
return new InMemoryStsClientStore();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.iam.identitytrust.sts.core.defaults.service;

import org.eclipse.edc.iam.identitytrust.sts.model.StsClient;
import org.eclipse.edc.iam.identitytrust.sts.service.StsClientService;
import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore;
import org.eclipse.edc.service.spi.result.ServiceResult;
import org.eclipse.edc.spi.security.Vault;
import org.eclipse.edc.transaction.spi.TransactionContext;

import java.util.Optional;

import static java.lang.String.format;

public class StsClientServiceImpl implements StsClientService {

private final StsClientStore stsClientStore;
private final TransactionContext transactionContext;
private final Vault vault;

public StsClientServiceImpl(StsClientStore stsClientStore, Vault vault, TransactionContext transactionContext) {
this.stsClientStore = stsClientStore;
this.vault = vault;
this.transactionContext = transactionContext;
}

@Override
public ServiceResult<StsClient> create(StsClient client) {
return transactionContext.execute(() -> ServiceResult.from(stsClientStore.create(client)));
}

@Override
public ServiceResult<StsClient> findById(String clientId) {
return transactionContext.execute(() -> ServiceResult.from(stsClientStore.findById(clientId)));
}

@Override
public ServiceResult<StsClient> authenticate(StsClient client, String secret) {
return Optional.ofNullable(vault.resolveSecret(client.getSecretAlias()))
.filter(vaultSecret -> vaultSecret.equals(secret))
.map(s -> ServiceResult.success(client))
.orElseGet(() -> ServiceResult.badRequest(format("Failed to authenticate client with id %s", client.getId())));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.iam.identitytrust.sts.core.defaults.service;

import org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService;
import org.eclipse.edc.iam.identitytrust.sts.model.StsClient;
import org.eclipse.edc.iam.identitytrust.sts.model.StsClientTokenAdditionalParams;
import org.eclipse.edc.iam.identitytrust.sts.service.StsClientTokenGeneratorService;
import org.eclipse.edc.iam.identitytrust.sts.service.StsTokenGenerationProvider;
import org.eclipse.edc.service.spi.result.ServiceResult;
import org.eclipse.edc.spi.iam.TokenRepresentation;

import java.time.Clock;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT;

public class StsClientTokenGeneratorServiceImpl implements StsClientTokenGeneratorService {

private final Map<String, EmbeddedSecureTokenService> tokenGenerations;
// TODO configurable?
private final Integer capacity = 100;
private final long tokenExpiration;
private final StsTokenGenerationProvider tokenGenerationProvider;
private final Clock clock;

public StsClientTokenGeneratorServiceImpl(StsTokenGenerationProvider tokenGenerationProvider, Clock clock, long tokenExpiration) {
this.tokenGenerationProvider = tokenGenerationProvider;
this.clock = clock;
this.tokenExpiration = tokenExpiration;
this.tokenGenerations = Collections.synchronizedMap(new LinkedHashMap<>(capacity, .75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, EmbeddedSecureTokenService> eldest) {
return size() > capacity;
}
});
}

@Override
public ServiceResult<TokenRepresentation> tokenFor(StsClient client, StsClientTokenAdditionalParams additionalParams) {
var embeddedTokenGenerator = tokenGenerations.computeIfAbsent(client.getId(), (id) -> new EmbeddedSecureTokenService(tokenGenerationProvider.tokenGeneratorFor(client), clock, tokenExpiration));

var claims = Map.of(
ISSUER, client.getId(),
SUBJECT, client.getId(),
AUDIENCE, additionalParams.getAudience(),
"client_id", client.getClientId());

var tokenResult = embeddedTokenGenerator.createToken(claims, additionalParams.getBearerAccessScope());

if (tokenResult.failed()) {
return ServiceResult.badRequest(tokenResult.getFailureDetail());
}
return ServiceResult.success(tokenResult.getContent());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.iam.identitytrust.sts.core.defaults.store;

import org.eclipse.edc.iam.identitytrust.sts.model.StsClient;
import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore;
import org.eclipse.edc.spi.result.StoreResult;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import static java.lang.String.format;

/**
* In memory implementation of {@link StsClientStore}
*/
public class InMemoryStsClientStore implements StsClientStore {

private final Map<String, StsClient> clients = new ConcurrentHashMap<>();

@Override
public StoreResult<StsClient> create(StsClient client) {
return Optional.ofNullable(clients.putIfAbsent(client.getId(), client))
.map(old -> StoreResult.<StsClient>alreadyExists(format("Client with id %s already exists", client.getId())))
.orElseGet(() -> StoreResult.success(client));
}

@Override
public StoreResult<StsClient> findById(String id) {
return Optional.ofNullable(clients.get(id))
.map(StoreResult::success)
.orElseGet(() -> StoreResult.notFound(format("Client with id %s not found.", id)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
#
# 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
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
#
#

org.eclipse.edc.iam.identitytrust.sts.core.StsDefaultServicesExtension
org.eclipse.edc.iam.identitytrust.sts.core.StsDefaultStoresExtension
Loading

0 comments on commit da1fb7a

Please sign in to comment.