Skip to content

Commit

Permalink
Added example on principal mapper usage
Browse files Browse the repository at this point in the history
  • Loading branch information
artem-v committed Jul 4, 2020
1 parent fa1c65e commit aebfe21
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import io.scalecube.services.auth.Authenticator;
import io.scalecube.services.auth.PrincipalMapper;
import io.scalecube.services.exceptions.BadRequestException;
import io.scalecube.services.exceptions.ForbiddenException;
import io.scalecube.services.exceptions.ServiceException;
import io.scalecube.services.exceptions.ServiceProviderErrorMapper;
import io.scalecube.services.exceptions.UnauthorizedException;
import io.scalecube.services.transport.api.ServiceMessageDataDecoder;
Expand All @@ -17,6 +15,7 @@
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
Expand Down Expand Up @@ -150,21 +149,12 @@ private Context enhanceContextWithPrincipal(Context context) {
principal = principalMapper.apply(context.get(Authenticator.AUTH_CONTEXT_KEY));
} catch (Exception ex) {
LOGGER.error("[principalMapper][{}] Exception occurred: {}", principalMapper, ex.toString());
throw toForbiddenException(ex);
throw Exceptions.propagate(ex);
}

return principal != null ? context.put(Authenticator.AUTH_CONTEXT_KEY, principal) : context;
}

private ForbiddenException toForbiddenException(Throwable th) {
if (th instanceof ServiceException) {
ServiceException e = (ServiceException) th;
return new ForbiddenException(e.errorCode(), e.getMessage());
} else {
return new ForbiddenException(th);
}
}

private Object toRequest(ServiceMessage message) {
ServiceMessage request = dataDecoder.apply(message, methodInfo.requestType());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.scalecube.services.examples.auth;

import java.util.StringJoiner;

public class ApiKey {

private final String id;
private final String permissions;

public ApiKey(String id, String permissions) {
this.id = id;
this.permissions = permissions;
}

public String id() {
return id;
}

public String permissions() {
return permissions;
}

@Override
public String toString() {
return new StringJoiner(", ", ApiKey.class.getSimpleName() + "[", "]")
.add("id='" + id + "'")
.add("permissions='" + permissions + "'")
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package io.scalecube.services.examples.auth;

import io.scalecube.services.Microservices;
import io.scalecube.services.ServiceEndpoint;
import io.scalecube.services.ServiceInfo;
import io.scalecube.services.auth.Authenticator;
import io.scalecube.services.auth.CredentialsSupplier;
import io.scalecube.services.discovery.ScalecubeServiceDiscovery;
import io.scalecube.services.exceptions.UnauthorizedException;
import io.scalecube.services.transport.rsocket.RSocketServiceTransport;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import reactor.core.publisher.Mono;

public class PrincipalMapperAuthExample {

/**
* Main program.
*
* @param args arguments
*/
public static void main(String[] args) {
Microservices service =
Microservices.builder()
.discovery("service", ScalecubeServiceDiscovery::new)
.transport(() -> new RSocketServiceTransport().authenticator(authenticator()))
.services(
ServiceInfo.fromServiceInstance(new SecuredServiceByApiKeyImpl())
.principalMapper(PrincipalMapperAuthExample::apiKeyPrincipalMapper)
.build())
.services(
ServiceInfo.fromServiceInstance(new SecuredServiceByUserProfileImpl())
.principalMapper(PrincipalMapperAuthExample::userProfilePrincipalMapper)
.build())
.startAwait();

Microservices userProfileCaller =
Microservices.builder()
.discovery("caller", endpoint -> discovery(service, endpoint))
.transport(() -> new RSocketServiceTransport().credentialsSupplier(credsSupplier()))
.startAwait();

String responseByUserProfile =
userProfileCaller
.call()
.api(SecuredServiceByUserProfile.class)
.hello(UUID.randomUUID().toString())
.block(Duration.ofSeconds(3));

System.err.println("### Received 'userProfileCaller' response: " + responseByUserProfile);

Microservices apiKeyCaller =
Microservices.builder()
.discovery("caller", endpoint -> discovery(service, endpoint))
.transport(() -> new RSocketServiceTransport().credentialsSupplier(credsSupplier()))
.startAwait();

String responseByApiKey =
apiKeyCaller
.call()
.api(SecuredServiceByApiKey.class)
.hello(UUID.randomUUID().toString())
.block(Duration.ofSeconds(3));

System.err.println("### Received 'apiKeyCaller' response: " + responseByApiKey);
}

private static Authenticator<Map<String, String>> authenticator() {
return headers -> {
String credsType = headers.get("type"); // credentials type

if (SecuredServiceByUserProfile.class.getName().equals(credsType)) {
return authenticateUserProfile(headers);
}
if (SecuredServiceByApiKey.class.getName().equals(credsType)) {
return authenticateApiKey(headers);
}
throw new IllegalArgumentException(
"[authenticator] not expected namespace: '" + credsType + "'");
};
}

private static CredentialsSupplier credsSupplier() {
return service -> {
String namespace = service.namespace(); // decide by service

if (SecuredServiceByUserProfile.class.getName().equals(namespace)) {
return userProfileCredentials();
}
if (SecuredServiceByApiKey.class.getName().equals(namespace)) {
return apiKeyCredentials();
}
throw new IllegalArgumentException(
"[credentialsSupplier] not expected namespace: '" + namespace + "'");
};
}

private static Mono<Map<String, String>> authenticateUserProfile(Map<String, String> headers) {
String username = headers.get("username");
String password = headers.get("password");

if ("Alice".equals(username) && "qwerty".equals(password)) {
HashMap<String, String> authData = new HashMap<>();
authData.put("name", "Alice");
authData.put("role", "ADMIN");
return Mono.just(authData);
}

return Mono.error(
new UnauthorizedException("Authentication failed (username or password incorrect)"));
}

private static Mono<Map<String, String>> authenticateApiKey(Map<String, String> headers) {
String apiKey = headers.get("apiKey");

if ("jasds8fjasdfjasd89fa4k9rjn7ahdfasduf".equals(apiKey)) {
HashMap<String, String> authData = new HashMap<>();
authData.put("id", "jasds8fjasdfjasd89fa4k9rjn7ahdfasduf");
authData.put("permissions", "OPERATIONS:EVENTS:ACTIONS");
return Mono.just(authData);
}

return Mono.error(new UnauthorizedException("Authentication failed (apiKey incorrect)"));
}

private static Mono<Map<String, String>> userProfileCredentials() {
HashMap<String, String> creds = new HashMap<>();
creds.put("type", SecuredServiceByUserProfile.class.getName());
creds.put("username", "Alice");
creds.put("password", "qwerty");
return Mono.just(creds);
}

private static Mono<Map<String, String>> apiKeyCredentials() {
HashMap<String, String> creds = new HashMap<>();
creds.put("type", SecuredServiceByApiKey.class.getName());
creds.put("apiKey", "jasds8fjasdfjasd89fa4k9rjn7ahdfasduf");
return Mono.just(creds);
}

private static UserProfile userProfilePrincipalMapper(Map<String, String> authData) {
return new UserProfile(authData.get("name"), authData.get("role"));
}

private static ApiKey apiKeyPrincipalMapper(Map<String, String> authData) {
return new ApiKey(authData.get("id"), authData.get("permissions"));
}

private static ScalecubeServiceDiscovery discovery(
Microservices service, ServiceEndpoint endpoint) {
return new ScalecubeServiceDiscovery(endpoint)
.membership(opts -> opts.seedMembers(service.discovery("service").address()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
import reactor.core.publisher.Mono;

@Secured
@Service(SecuredService.SERVICE_NAME)
public interface SecuredService {

String SERVICE_NAME = "secured";
@Service
public interface SecuredServiceByApiKey {

@ServiceMethod
Mono<String> securedHello(String name);
Mono<String> hello(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.scalecube.services.examples.auth;

import io.scalecube.services.auth.MonoAuthUtil;
import io.scalecube.services.exceptions.ForbiddenException;
import reactor.core.publisher.Mono;

public class SecuredServiceByApiKeyImpl implements SecuredServiceByApiKey {

@Override
public Mono<String> hello(String name) {
return MonoAuthUtil.deferWithPrincipal(ApiKey.class)
.flatMap(
apiKey -> {
checkPermissions(apiKey);
return Mono.just("Hello, name=" + name + " (apiKey=" + apiKey + ")");
});
}

private void checkPermissions(ApiKey apiKey) {
if (!apiKey.permissions().equals("OPERATIONS:EVENTS:ACTIONS")) {
throw new ForbiddenException("Forbidden");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.scalecube.services.examples.auth;

import io.scalecube.services.annotations.Service;
import io.scalecube.services.annotations.ServiceMethod;
import io.scalecube.services.auth.Secured;
import reactor.core.publisher.Mono;

@Secured
@Service
public interface SecuredServiceByUserProfile {

@ServiceMethod
Mono<String> hello(String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
import io.scalecube.services.exceptions.ForbiddenException;
import reactor.core.publisher.Mono;

public class SecuredServiceImpl implements SecuredService {
public class SecuredServiceByUserProfileImpl implements SecuredServiceByUserProfile {

@Override
public Mono<String> securedHello(String name) {
public Mono<String> hello(String name) {
return MonoAuthUtil.deferWithPrincipal(UserProfile.class)
.flatMap(
user -> {
checkPrincipal(user);
return Mono.just("Hello, name=" + name + " and user.name=" + user.name());
checkPermissions(user);
return Mono.just("Hello, name=" + name + " (user=" + user + ")");
});
}

private void checkPrincipal(UserProfile user) {
private void checkPermissions(UserProfile user) {
if (!user.role().equals("ADMIN")) {
throw new ForbiddenException("Forbidden");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.scalecube.services.Microservices;
import io.scalecube.services.ServiceEndpoint;
import io.scalecube.services.ServiceInfo;
import io.scalecube.services.auth.Authenticator;
import io.scalecube.services.auth.CredentialsSupplier;
import io.scalecube.services.discovery.ScalecubeServiceDiscovery;
Expand All @@ -25,7 +24,7 @@ public static void main(String[] args) {
Microservices.builder()
.discovery("service", ScalecubeServiceDiscovery::new)
.transport(() -> new RSocketServiceTransport().authenticator(authenticator()))
.services(ServiceInfo.fromServiceInstance(new SecuredServiceImpl()).build())
.services(new SecuredServiceByUserProfileImpl())
.startAwait();

Microservices caller =
Expand All @@ -34,14 +33,14 @@ public static void main(String[] args) {
.transport(() -> new RSocketServiceTransport().credentialsSupplier(credsSupplier()))
.startAwait();

String hello =
String response =
caller
.call()
.api(SecuredService.class)
.securedHello(UUID.randomUUID().toString())
.api(SecuredServiceByUserProfile.class)
.hello(UUID.randomUUID().toString())
.block(Duration.ofSeconds(3));

System.err.println("### Received 'secured hello' response: " + hello);
System.err.println("### Received 'caller' response: " + response);
}

private static Authenticator<UserProfile> authenticator() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.scalecube.services.examples.auth;

import java.util.StringJoiner;

public class UserProfile {

private final String name;
Expand All @@ -17,4 +19,12 @@ public String name() {
public String role() {
return role;
}

@Override
public String toString() {
return new StringJoiner(", ", UserProfile.class.getSimpleName() + "[", "]")
.add("name='" + name + "'")
.add("role='" + role + "'")
.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,12 @@
</Appenders>

<Loggers>
<Logger name="io.scalecube.services.Microservices" level="warn"/>
<Logger name="io.scalecube.seed" level="${env:seedLogLevel:-INFO}"/>
<Logger name="io.scalecube.services" level="${env:servicesLogLevel:-INFO}"/>
<Logger name="io.scalecube.transport" level="${env:servicesTransportLogLevel:-DEBUG}"/>
<Logger name="io.scalecube.cluster" level="${env:clusterLogLevel:-INFO}"/>
<Logger name="io.scalecube.services.transport" level="${env:servicesTransportLogLevel:-debug}"/>
<Logger name="io.scalecube.cluster" level="${env:clusterLogLevel:-warn}"/>
<Logger name="io.scalecube.cluster.transport" level="${env:clusterTransportLogLevel:-error}"/>
<Logger name="io.scalecube.config" level="${env:configLogLevel:-INFO}"/>
<Logger name="reactor.util" level="${env:reactorUtilLogLevel:-WARN}"/>
<Logger name="reactor.core" level="${env:reactorCoreLogLevel:-WARN}"/>
Expand Down

0 comments on commit aebfe21

Please sign in to comment.