Skip to content

Commit aebfe21

Browse files
committed
Added example on principal mapper usage
1 parent fa1c65e commit aebfe21

File tree

10 files changed

+253
-30
lines changed

10 files changed

+253
-30
lines changed

services-api/src/main/java/io/scalecube/services/methods/ServiceMethodInvoker.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import io.scalecube.services.auth.Authenticator;
55
import io.scalecube.services.auth.PrincipalMapper;
66
import io.scalecube.services.exceptions.BadRequestException;
7-
import io.scalecube.services.exceptions.ForbiddenException;
8-
import io.scalecube.services.exceptions.ServiceException;
97
import io.scalecube.services.exceptions.ServiceProviderErrorMapper;
108
import io.scalecube.services.exceptions.UnauthorizedException;
119
import io.scalecube.services.transport.api.ServiceMessageDataDecoder;
@@ -17,6 +15,7 @@
1715
import org.reactivestreams.Publisher;
1816
import org.slf4j.Logger;
1917
import org.slf4j.LoggerFactory;
18+
import reactor.core.Exceptions;
2019
import reactor.core.publisher.Flux;
2120
import reactor.core.publisher.Mono;
2221
import reactor.util.context.Context;
@@ -150,21 +149,12 @@ private Context enhanceContextWithPrincipal(Context context) {
150149
principal = principalMapper.apply(context.get(Authenticator.AUTH_CONTEXT_KEY));
151150
} catch (Exception ex) {
152151
LOGGER.error("[principalMapper][{}] Exception occurred: {}", principalMapper, ex.toString());
153-
throw toForbiddenException(ex);
152+
throw Exceptions.propagate(ex);
154153
}
155154

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

159-
private ForbiddenException toForbiddenException(Throwable th) {
160-
if (th instanceof ServiceException) {
161-
ServiceException e = (ServiceException) th;
162-
return new ForbiddenException(e.errorCode(), e.getMessage());
163-
} else {
164-
return new ForbiddenException(th);
165-
}
166-
}
167-
168158
private Object toRequest(ServiceMessage message) {
169159
ServiceMessage request = dataDecoder.apply(message, methodInfo.requestType());
170160

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.scalecube.services.examples.auth;
2+
3+
import java.util.StringJoiner;
4+
5+
public class ApiKey {
6+
7+
private final String id;
8+
private final String permissions;
9+
10+
public ApiKey(String id, String permissions) {
11+
this.id = id;
12+
this.permissions = permissions;
13+
}
14+
15+
public String id() {
16+
return id;
17+
}
18+
19+
public String permissions() {
20+
return permissions;
21+
}
22+
23+
@Override
24+
public String toString() {
25+
return new StringJoiner(", ", ApiKey.class.getSimpleName() + "[", "]")
26+
.add("id='" + id + "'")
27+
.add("permissions='" + permissions + "'")
28+
.toString();
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package io.scalecube.services.examples.auth;
2+
3+
import io.scalecube.services.Microservices;
4+
import io.scalecube.services.ServiceEndpoint;
5+
import io.scalecube.services.ServiceInfo;
6+
import io.scalecube.services.auth.Authenticator;
7+
import io.scalecube.services.auth.CredentialsSupplier;
8+
import io.scalecube.services.discovery.ScalecubeServiceDiscovery;
9+
import io.scalecube.services.exceptions.UnauthorizedException;
10+
import io.scalecube.services.transport.rsocket.RSocketServiceTransport;
11+
import java.time.Duration;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
import java.util.UUID;
15+
import reactor.core.publisher.Mono;
16+
17+
public class PrincipalMapperAuthExample {
18+
19+
/**
20+
* Main program.
21+
*
22+
* @param args arguments
23+
*/
24+
public static void main(String[] args) {
25+
Microservices service =
26+
Microservices.builder()
27+
.discovery("service", ScalecubeServiceDiscovery::new)
28+
.transport(() -> new RSocketServiceTransport().authenticator(authenticator()))
29+
.services(
30+
ServiceInfo.fromServiceInstance(new SecuredServiceByApiKeyImpl())
31+
.principalMapper(PrincipalMapperAuthExample::apiKeyPrincipalMapper)
32+
.build())
33+
.services(
34+
ServiceInfo.fromServiceInstance(new SecuredServiceByUserProfileImpl())
35+
.principalMapper(PrincipalMapperAuthExample::userProfilePrincipalMapper)
36+
.build())
37+
.startAwait();
38+
39+
Microservices userProfileCaller =
40+
Microservices.builder()
41+
.discovery("caller", endpoint -> discovery(service, endpoint))
42+
.transport(() -> new RSocketServiceTransport().credentialsSupplier(credsSupplier()))
43+
.startAwait();
44+
45+
String responseByUserProfile =
46+
userProfileCaller
47+
.call()
48+
.api(SecuredServiceByUserProfile.class)
49+
.hello(UUID.randomUUID().toString())
50+
.block(Duration.ofSeconds(3));
51+
52+
System.err.println("### Received 'userProfileCaller' response: " + responseByUserProfile);
53+
54+
Microservices apiKeyCaller =
55+
Microservices.builder()
56+
.discovery("caller", endpoint -> discovery(service, endpoint))
57+
.transport(() -> new RSocketServiceTransport().credentialsSupplier(credsSupplier()))
58+
.startAwait();
59+
60+
String responseByApiKey =
61+
apiKeyCaller
62+
.call()
63+
.api(SecuredServiceByApiKey.class)
64+
.hello(UUID.randomUUID().toString())
65+
.block(Duration.ofSeconds(3));
66+
67+
System.err.println("### Received 'apiKeyCaller' response: " + responseByApiKey);
68+
}
69+
70+
private static Authenticator<Map<String, String>> authenticator() {
71+
return headers -> {
72+
String credsType = headers.get("type"); // credentials type
73+
74+
if (SecuredServiceByUserProfile.class.getName().equals(credsType)) {
75+
return authenticateUserProfile(headers);
76+
}
77+
if (SecuredServiceByApiKey.class.getName().equals(credsType)) {
78+
return authenticateApiKey(headers);
79+
}
80+
throw new IllegalArgumentException(
81+
"[authenticator] not expected namespace: '" + credsType + "'");
82+
};
83+
}
84+
85+
private static CredentialsSupplier credsSupplier() {
86+
return service -> {
87+
String namespace = service.namespace(); // decide by service
88+
89+
if (SecuredServiceByUserProfile.class.getName().equals(namespace)) {
90+
return userProfileCredentials();
91+
}
92+
if (SecuredServiceByApiKey.class.getName().equals(namespace)) {
93+
return apiKeyCredentials();
94+
}
95+
throw new IllegalArgumentException(
96+
"[credentialsSupplier] not expected namespace: '" + namespace + "'");
97+
};
98+
}
99+
100+
private static Mono<Map<String, String>> authenticateUserProfile(Map<String, String> headers) {
101+
String username = headers.get("username");
102+
String password = headers.get("password");
103+
104+
if ("Alice".equals(username) && "qwerty".equals(password)) {
105+
HashMap<String, String> authData = new HashMap<>();
106+
authData.put("name", "Alice");
107+
authData.put("role", "ADMIN");
108+
return Mono.just(authData);
109+
}
110+
111+
return Mono.error(
112+
new UnauthorizedException("Authentication failed (username or password incorrect)"));
113+
}
114+
115+
private static Mono<Map<String, String>> authenticateApiKey(Map<String, String> headers) {
116+
String apiKey = headers.get("apiKey");
117+
118+
if ("jasds8fjasdfjasd89fa4k9rjn7ahdfasduf".equals(apiKey)) {
119+
HashMap<String, String> authData = new HashMap<>();
120+
authData.put("id", "jasds8fjasdfjasd89fa4k9rjn7ahdfasduf");
121+
authData.put("permissions", "OPERATIONS:EVENTS:ACTIONS");
122+
return Mono.just(authData);
123+
}
124+
125+
return Mono.error(new UnauthorizedException("Authentication failed (apiKey incorrect)"));
126+
}
127+
128+
private static Mono<Map<String, String>> userProfileCredentials() {
129+
HashMap<String, String> creds = new HashMap<>();
130+
creds.put("type", SecuredServiceByUserProfile.class.getName());
131+
creds.put("username", "Alice");
132+
creds.put("password", "qwerty");
133+
return Mono.just(creds);
134+
}
135+
136+
private static Mono<Map<String, String>> apiKeyCredentials() {
137+
HashMap<String, String> creds = new HashMap<>();
138+
creds.put("type", SecuredServiceByApiKey.class.getName());
139+
creds.put("apiKey", "jasds8fjasdfjasd89fa4k9rjn7ahdfasduf");
140+
return Mono.just(creds);
141+
}
142+
143+
private static UserProfile userProfilePrincipalMapper(Map<String, String> authData) {
144+
return new UserProfile(authData.get("name"), authData.get("role"));
145+
}
146+
147+
private static ApiKey apiKeyPrincipalMapper(Map<String, String> authData) {
148+
return new ApiKey(authData.get("id"), authData.get("permissions"));
149+
}
150+
151+
private static ScalecubeServiceDiscovery discovery(
152+
Microservices service, ServiceEndpoint endpoint) {
153+
return new ScalecubeServiceDiscovery(endpoint)
154+
.membership(opts -> opts.seedMembers(service.discovery("service").address()));
155+
}
156+
}
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
import reactor.core.publisher.Mono;
77

88
@Secured
9-
@Service(SecuredService.SERVICE_NAME)
10-
public interface SecuredService {
11-
12-
String SERVICE_NAME = "secured";
9+
@Service
10+
public interface SecuredServiceByApiKey {
1311

1412
@ServiceMethod
15-
Mono<String> securedHello(String name);
13+
Mono<String> hello(String name);
1614
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.scalecube.services.examples.auth;
2+
3+
import io.scalecube.services.auth.MonoAuthUtil;
4+
import io.scalecube.services.exceptions.ForbiddenException;
5+
import reactor.core.publisher.Mono;
6+
7+
public class SecuredServiceByApiKeyImpl implements SecuredServiceByApiKey {
8+
9+
@Override
10+
public Mono<String> hello(String name) {
11+
return MonoAuthUtil.deferWithPrincipal(ApiKey.class)
12+
.flatMap(
13+
apiKey -> {
14+
checkPermissions(apiKey);
15+
return Mono.just("Hello, name=" + name + " (apiKey=" + apiKey + ")");
16+
});
17+
}
18+
19+
private void checkPermissions(ApiKey apiKey) {
20+
if (!apiKey.permissions().equals("OPERATIONS:EVENTS:ACTIONS")) {
21+
throw new ForbiddenException("Forbidden");
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.scalecube.services.examples.auth;
2+
3+
import io.scalecube.services.annotations.Service;
4+
import io.scalecube.services.annotations.ServiceMethod;
5+
import io.scalecube.services.auth.Secured;
6+
import reactor.core.publisher.Mono;
7+
8+
@Secured
9+
@Service
10+
public interface SecuredServiceByUserProfile {
11+
12+
@ServiceMethod
13+
Mono<String> hello(String name);
14+
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
import io.scalecube.services.exceptions.ForbiddenException;
55
import reactor.core.publisher.Mono;
66

7-
public class SecuredServiceImpl implements SecuredService {
7+
public class SecuredServiceByUserProfileImpl implements SecuredServiceByUserProfile {
88

99
@Override
10-
public Mono<String> securedHello(String name) {
10+
public Mono<String> hello(String name) {
1111
return MonoAuthUtil.deferWithPrincipal(UserProfile.class)
1212
.flatMap(
1313
user -> {
14-
checkPrincipal(user);
15-
return Mono.just("Hello, name=" + name + " and user.name=" + user.name());
14+
checkPermissions(user);
15+
return Mono.just("Hello, name=" + name + " (user=" + user + ")");
1616
});
1717
}
1818

19-
private void checkPrincipal(UserProfile user) {
19+
private void checkPermissions(UserProfile user) {
2020
if (!user.role().equals("ADMIN")) {
2121
throw new ForbiddenException("Forbidden");
2222
}

services-examples-parent/services-examples/src/main/java/io/scalecube/services/examples/auth/ServiceTransportAuthExample.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import io.scalecube.services.Microservices;
44
import io.scalecube.services.ServiceEndpoint;
5-
import io.scalecube.services.ServiceInfo;
65
import io.scalecube.services.auth.Authenticator;
76
import io.scalecube.services.auth.CredentialsSupplier;
87
import io.scalecube.services.discovery.ScalecubeServiceDiscovery;
@@ -25,7 +24,7 @@ public static void main(String[] args) {
2524
Microservices.builder()
2625
.discovery("service", ScalecubeServiceDiscovery::new)
2726
.transport(() -> new RSocketServiceTransport().authenticator(authenticator()))
28-
.services(ServiceInfo.fromServiceInstance(new SecuredServiceImpl()).build())
27+
.services(new SecuredServiceByUserProfileImpl())
2928
.startAwait();
3029

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

37-
String hello =
36+
String response =
3837
caller
3938
.call()
40-
.api(SecuredService.class)
41-
.securedHello(UUID.randomUUID().toString())
39+
.api(SecuredServiceByUserProfile.class)
40+
.hello(UUID.randomUUID().toString())
4241
.block(Duration.ofSeconds(3));
4342

44-
System.err.println("### Received 'secured hello' response: " + hello);
43+
System.err.println("### Received 'caller' response: " + response);
4544
}
4645

4746
private static Authenticator<UserProfile> authenticator() {

services-examples-parent/services-examples/src/main/java/io/scalecube/services/examples/auth/UserProfile.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.scalecube.services.examples.auth;
22

3+
import java.util.StringJoiner;
4+
35
public class UserProfile {
46

57
private final String name;
@@ -17,4 +19,12 @@ public String name() {
1719
public String role() {
1820
return role;
1921
}
22+
23+
@Override
24+
public String toString() {
25+
return new StringJoiner(", ", UserProfile.class.getSimpleName() + "[", "]")
26+
.add("name='" + name + "'")
27+
.add("role='" + role + "'")
28+
.toString();
29+
}
2030
}

services-examples-parent/services-examples/src/main/resources/log4j2.xml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@
4646
</Appenders>
4747

4848
<Loggers>
49+
<Logger name="io.scalecube.services.Microservices" level="warn"/>
4950
<Logger name="io.scalecube.seed" level="${env:seedLogLevel:-INFO}"/>
5051
<Logger name="io.scalecube.services" level="${env:servicesLogLevel:-INFO}"/>
51-
<Logger name="io.scalecube.transport" level="${env:servicesTransportLogLevel:-DEBUG}"/>
52-
<Logger name="io.scalecube.cluster" level="${env:clusterLogLevel:-INFO}"/>
52+
<Logger name="io.scalecube.services.transport" level="${env:servicesTransportLogLevel:-debug}"/>
53+
<Logger name="io.scalecube.cluster" level="${env:clusterLogLevel:-warn}"/>
54+
<Logger name="io.scalecube.cluster.transport" level="${env:clusterTransportLogLevel:-error}"/>
5355
<Logger name="io.scalecube.config" level="${env:configLogLevel:-INFO}"/>
5456
<Logger name="reactor.util" level="${env:reactorUtilLogLevel:-WARN}"/>
5557
<Logger name="reactor.core" level="${env:reactorCoreLogLevel:-WARN}"/>

0 commit comments

Comments
 (0)