Skip to content

Commit

Permalink
Auth Callout
Browse files Browse the repository at this point in the history
  • Loading branch information
scottf committed Jan 8, 2024
1 parent 6c6c341 commit e1388f1
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 202 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package io.nats.examples.service;
package io.nats.examples.authcallout;

import io.nats.client.*;
import io.nats.client.impl.Headers;
Expand All @@ -24,48 +24,38 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import static io.nats.jwt.Utils.currentTimeSeconds;
import static io.nats.jwt.Utils.getClaimBody;

/**
* This example demonstrates basic setup and use of the Service Framework
*/
public class AuthCalloutServiceExample {
static String ISSUER_NKEY = "ABJHLOVMPA4CI6R5KLNGOB4GSLNIY7IOUPAJC4YFNDLQVIOBYQGUWVLA";
static String ISSUER_NSEED = "SAANDLKMXL6CUS3CP52WIXBEDN6YJ545GDKC65U5JZPPV6WH6ESWUA6YAI";

static final Map<String, NatsUser> NATS_USERS;
static final Map<String, AuthCalloutUser> NATS_USERS;

// static final NKey USER_KEY;
static final NKey USER_SIGNING_KEY;
// static final String PUB_USER_KEY;
static final String PUB_USER_SIGNING_KEY;

static {
try {
// USER_KEY = NKey.fromSeed(ISSUER_NKEY.toCharArray());
// PUB_USER_KEY = new String(USER_KEY.getPublicKey());
USER_SIGNING_KEY = NKey.fromSeed(ISSUER_NSEED.toCharArray());
PUB_USER_SIGNING_KEY = new String(USER_SIGNING_KEY.getPublicKey());
}
catch (Exception e) {
throw new RuntimeException(e);
}
// curveNKey = NKey.fromSeed(ISSUER_XSEED.toCharArray());

// sys/sys, SYS
// alice/alice, APP
// bob/bob, APP, pub allow "bob.>", sub allow "bob.>", response max 1
// This sets up a map of users to simulate a back end auth system
// sys/sys, SYS
// alice/alice, APP
// bob/bob, APP, pub allow "bob.>", sub allow "bob.>", response max 1
NATS_USERS = new HashMap<>();
NATS_USERS.put("sys", new NatsUser().userPass("sys").account("SYS"));
NATS_USERS.put("alice", new NatsUser().userPass("alice").account("APP"));
NATS_USERS.put("sys", new AuthCalloutUser().userPass("sys").account("SYS"));
NATS_USERS.put("alice", new AuthCalloutUser().userPass("alice").account("APP"));
Permission p = new Permission().allow("bob.>");
ResponsePermission r = new ResponsePermission().max(1);
NATS_USERS.put("bob", new NatsUser().userPass("bob").account("APP").pub(p).sub(p).resp(r));
NATS_USERS.put("bob", new AuthCalloutUser().userPass("bob").account("APP").pub(p).sub(p).resp(r));
}

public static void main(String[] args) throws Exception {

Options options = new Options.Builder()
.server("nats://localhost:4222")
.errorListener(new ErrorListener() {})
Expand All @@ -75,76 +65,39 @@ public static void main(String[] args) throws Exception {
try (Connection nc = Nats.connect(options)) {
// endpoints can be created ahead of time
// or created directly by the ServiceEndpoint builder.
Endpoint epAuthCallout = Endpoint.builder()
.name("AuthEndpoint")
Endpoint endpoint = Endpoint.builder()
.name("AuthCallbackEndpoint")
.subject("$SYS.REQ.USER.AUTH")
.build();

AuthCalloutHandler handler = new AuthCalloutHandler(nc);
ServiceEndpoint seAuthCallout = ServiceEndpoint.builder()
.endpoint(epAuthCallout)

ServiceEndpoint serviceEndpoint = ServiceEndpoint.builder()
.endpoint(endpoint)
.handler(handler)
.build();

// Create the service from service endpoints.
Service service1 = new ServiceBuilder()
// Create the service from service endpoint.
Service acService = new ServiceBuilder()
.connection(nc)
.name("AuthService")
.name("AuthCallbackService")
.version("0.0.1")
.addServiceEndpoint(seAuthCallout)
.addServiceEndpoint(serviceEndpoint)
.build();

System.out.println("\n" + service1);
System.out.println("\n" + acService);

// ----------------------------------------------------------------------------------------------------
// Start the services
// ----------------------------------------------------------------------------------------------------
CompletableFuture<Boolean> serviceStoppedFuture = service1.startService();

AuthCalloutUserExample.main("alicex", "alice");
AuthCalloutUserExample.main("alice", "alice");
CompletableFuture<Boolean> serviceStoppedFuture = acService.startService();
serviceStoppedFuture.get(); // waits indefinitely since there is nothing calling stop
}
catch (Exception e) {
e.printStackTrace();
onException(e);
}
}

/*
{
"aud": "nats-authorization-request",
"jti": "43CCBS2OARGHVNIMBI2IEGVXSCT2L25WNQ4D2BF2T24DWORJLE2A",
"iat": 1704653960,
"iss": "NC5WUN42IKPYZQI5EWREZOHGYHZA6XN7UE766VSNBV3AOPTYKAISY6XV",
"sub": "ABJHLOVMPA4CI6R5KLNGOB4GSLNIY7IOUPAJC4YFNDLQVIOBYQGUWVLA",
"exp": 1704653962,
"nats": {
"client_info": {
"kind": "Client",
"host": "127.0.0.1",
"id": 28,
"type": "nats",
"user": "alicex"
},
"connect_opts": {
"protocol": 1,
"pass": "alice",
"lang": "java",
"user": "alicex",
"version": "2.17.3.dev"
},
"server_id": {
"name": "NC5WUN42IKPYZQI5EWREZOHGYHZA6XN7UE766VSNBV3AOPTYKAISY6XV",
"host": "0.0.0.0",
"id": "NC5WUN42IKPYZQI5EWREZOHGYHZA6XN7UE766VSNBV3AOPTYKAISY6XV",
"version": "2.10.4"
},
"type": "authorization_request",
"version": 2,
"user_nkey": "UCPGU74LE4GOMJZFFD6Z4QJLSUGUJIQ23OH6IZUMRI3BXITCIPPFGW6D"
}
}
*/

static class AuthCalloutHandler implements ServiceMessageHandler {
Connection nc;

Expand All @@ -154,38 +107,42 @@ public AuthCalloutHandler(Connection nc) {

@Override
public void onMessage(ServiceMessage smsg) {
System.out.println("\nSubject : " + smsg.getSubject());
System.out.println("Headers : " + hToString(smsg.getHeaders()));
System.out.println("\nReceived Message");
System.out.println("Subject : " + smsg.getSubject());
System.out.println("Headers : " + headersToString(smsg.getHeaders()));

try {
// Convert the message data into a Claim
Claim claim = new Claim(getClaimBody(smsg.getData()));
System.out.println("Claim : " + claim.toJson());
System.out.println("Claim-Request : " + claim.toJson());

// The Claim should contain an Authorization Request
AuthorizationRequest ar = claim.authorizationRequest;
if (ar == null) {
System.err.println("Invalid Authorization Request Claim");
return;
}
System.out.println("Auth Req : " + ar.toJson());
printJson("Auth Request : ", ar.toJson(), "server_id", "user_nkey", "client_info", "connect_opts", "client_tls", "request_nonce");

// Check if the user exists.
NatsUser u = NATS_USERS.get(ar.connectOpts.user);
if (u == null) {
AuthCalloutUser acUser = NATS_USERS.get(ar.connectOpts.user);
if (acUser == null) {
respond(smsg, ar, null, "User Not Found: " + ar.connectOpts.user);
return;
}
if (!acUser.pass.equals(ar.connectOpts.pass)) {
respond(smsg, ar, null, "Password does not match: " + acUser.pass + " != " + ar.connectOpts.pass);
return;
}

UserClaim uc = new UserClaim()
.pub(u.pub)
.sub(u.sub)
.resp(u.resp);
.pub(acUser.pub)
.sub(acUser.sub)
.resp(acUser.resp);

String userJwt = new ClaimIssuer()
.aud(u.account)
.aud(acUser.account)
.name(ar.connectOpts.user)
.iat(currentTimeSeconds())
.iss(PUB_USER_SIGNING_KEY)
.sub(ar.userNkey)
.nats(uc)
Expand All @@ -198,24 +155,55 @@ public void onMessage(ServiceMessage smsg) {
}
}

static final String SPACER = " ";
private void printJson(String label, String json, String... splits) {
if (splits != null && splits.length > 0) {
String indent = SPACER.substring(0, label.length());
boolean first = true;
for (String split : splits) {
int at = json.indexOf("\"" + split + "\"");
if (at > 0) {
if (first) {
first = false;
System.out.println(label + json.substring(0, at));
}
else {
System.out.println(indent + json.substring(0, at));
}
json = json.substring(at);
}
}
System.out.println(indent + json);
}
else {
System.out.println(label + json);
}
}

private void respond(ServiceMessage smsg,
AuthorizationRequest ar,
String userJwt,
String error) throws GeneralSecurityException, IOException {

AuthorizationResponse response = new AuthorizationResponse()
.jwt(userJwt)
.error(error);
System.out.println("Auth Resp : " + response.toJson());

if (userJwt != null) {
printJson("Auth Resp JWT : ", getClaimBody(userJwt), "name", "nats");
}
else {
System.out.println("Auth Resp ERR : " + response.toJson());
}

String jwt = new ClaimIssuer()
.aud(ar.serverId.id)
.iat(currentTimeSeconds())
.iss(PUB_USER_SIGNING_KEY)
.sub(ar.userNkey)
.nats(response)
.issueJwt(USER_SIGNING_KEY);

System.out.println("Claim Resp : " + getClaimBody(jwt));
System.out.println("Claim-Response: " + getClaimBody(jwt));
smsg.respond(nc, jwt);
}
}
Expand All @@ -225,52 +213,7 @@ private static void onException(Exception e) {
e.printStackTrace();
}

static class NatsUser {
public String user;
public String pass;
public String account;
public Permission pub;
public Permission sub;
public ResponsePermission resp;

public NatsUser userPass(String userPass) {
this.user = userPass;
this.pass = userPass;
return this;
}

public NatsUser user(String user) {
this.user = user;
return this;
}

public NatsUser pass(String pass) {
this.pass = pass;
return this;
}

public NatsUser account(String account) {
this.account = account;
return this;
}

public NatsUser pub(Permission pub) {
this.pub = pub;
return this;
}

public NatsUser sub(Permission sub) {
this.sub = sub;
return this;
}

public NatsUser resp(ResponsePermission resp) {
this.resp = resp;
return this;
}
}

public static String hToString(Headers h) {
public static String headersToString(Headers h) {
if (h == null || h.isEmpty()) {
return "None";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.nats.examples.authcallout;

import io.nats.jwt.Permission;
import io.nats.jwt.ResponsePermission;

public class AuthCalloutUser {
public String user;
public String pass;
public String account;
public Permission pub;
public Permission sub;
public ResponsePermission resp;

public AuthCalloutUser userPass(String userPass) {
this.user = userPass;
this.pass = userPass;
return this;
}

public AuthCalloutUser user(String user) {
this.user = user;
return this;
}

public AuthCalloutUser pass(String pass) {
this.pass = pass;
return this;
}

public AuthCalloutUser account(String account) {
this.account = account;
return this;
}

public AuthCalloutUser pub(Permission pub) {
this.pub = pub;
return this;
}

public AuthCalloutUser sub(Permission sub) {
this.sub = sub;
return this;
}

public AuthCalloutUser resp(ResponsePermission resp) {
this.resp = resp;
return this;
}
}
Loading

0 comments on commit e1388f1

Please sign in to comment.