SpringBoot: HoldingIdentity DAO object

This commit is contained in:
djmil 2023-08-31 15:47:00 +02:00
parent 0663fbacd7
commit 806c619588
13 changed files with 137 additions and 104 deletions

View File

@ -9,20 +9,20 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import djmil.cordacheckers.cordaclient.CordaClient; import djmil.cordacheckers.cordaclient.CordaClient;
import djmil.cordacheckers.cordaclient.pojo.virtualNodes; import djmil.cordacheckers.cordaclient.dao.VirtualNode;
@RestController @RestController
public class ApiController { public class ApiController {
@Autowired @Autowired
CordaClient cordaclient; CordaClient cordaClient;
@GetMapping("/api/badjokes") @GetMapping("/api/badjokes")
public ResponseEntity<Joke> badJokes() { public ResponseEntity<Joke> badJokes() {
List<virtualNodes> vNodes = cordaclient.getVirtualnode(); List<VirtualNode> vNodeList = cordaClient.getVirtualNodeList();
Joke joke = new Joke("What do you call a fly without wings? A walk! " + vNodes.get(1).holdingIdentity().x500Name()); Joke joke = new Joke("What do you call a fly without wings? A walk! " + vNodeList.get(1).holdingIdentity().x500Name());
return ResponseEntity.ok(joke); return ResponseEntity.ok(joke);
} }
@ -35,9 +35,9 @@ public class ApiController {
public ResponseEntity<String> dashboard(@AuthenticationPrincipal ApiUserDetails user) { public ResponseEntity<String> dashboard(@AuthenticationPrincipal ApiUserDetails user) {
System.out.println("List of active games for " System.out.println("List of active games for "
+ "user: " + user.getUsername() + "user: " + user.getUsername()
+ " with shortIdentityHash: " + user.getHoldingIdentityShortHash()); + " with HoldingIdentity ShortHash: " + user.getHoldingIdentity().shortHash());
return ResponseEntity.ok("{ \"ActiveGames\" : [\"id_game1\", \"id_game2\"] }" ); return ResponseEntity.ok("{ \"UnconsumedGameProposals\" : [\"id_game1\", \"id_game2\"] }" );
} }
} }

View File

@ -3,17 +3,19 @@ package djmil.cordacheckers;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
public class ApiUserDetails extends User { import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
private final String holdingIdentityShortHash;
public ApiUserDetails(UserDetails user, String holdingIdentityShortHash) { public class ApiUserDetails extends User {
private final HoldingIdentity holdingIdentity;
public ApiUserDetails(UserDetails user, HoldingIdentity holdingIdentity) {
super(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities()); super(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
this.holdingIdentityShortHash = holdingIdentityShortHash; this.holdingIdentity = holdingIdentity;
} }
public String getHoldingIdentityShortHash() { public HoldingIdentity getHoldingIdentity() {
return this.holdingIdentityShortHash; return this.holdingIdentity;
} }
} }

View File

@ -7,25 +7,27 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import djmil.cordacheckers.cordaclient.HoldingIdentityResolver;
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
@Service @Service
public class ApiUserDetailsService implements UserDetailsService { public class ApiUserDetailsService implements UserDetailsService {
private final PasswordEncoder encoder; private final PasswordEncoder encoder;
private final HoldingIdentityNameResolver holdingIdentityNameResolver; private final HoldingIdentityResolver holdingIdentityResolver;
public ApiUserDetailsService( public ApiUserDetailsService(
PasswordEncoder encoder, PasswordEncoder encoder,
HoldingIdentityNameResolver holdingIdentityNameResolver) { HoldingIdentityResolver holdingIdentityNameResolver) {
this.encoder = encoder; this.encoder = encoder;
this.holdingIdentityNameResolver = holdingIdentityNameResolver; this.holdingIdentityResolver = holdingIdentityNameResolver;
} }
@Override @Override
public ApiUserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public ApiUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String shortHash = holdingIdentityNameResolver.getShortHashBy(username); HoldingIdentity holdingIdentity = holdingIdentityResolver.getByCommonName(username);
if (shortHash == null) { if (holdingIdentity == null) {
throw new UsernameNotFoundException("ShortHash for user '" throw new UsernameNotFoundException("Can't find HoldingIdentity for the user '"+username+ "'");
+username+ "' not found");
} }
System.out.println("Load user "+username); System.out.println("Load user "+username);
@ -36,6 +38,6 @@ public class ApiUserDetailsService implements UserDetailsService {
.password(encoder.encode("qaz123")) .password(encoder.encode("qaz123"))
.build(); .build();
return new ApiUserDetails(user, shortHash); return new ApiUserDetails(user, holdingIdentity);
} }
} }

View File

@ -1,51 +0,0 @@
package djmil.cordacheckers;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.naming.InvalidNameException;
import org.springframework.stereotype.Service;
import djmil.cordacheckers.cordaclient.CordaClient;
import djmil.cordacheckers.cordaclient.pojo.virtualNodes;
@Service
public class HoldingIdentityNameResolver {
static final Locale locale = Locale.getDefault();
Map<String, String> cnName2shortHash;
HoldingIdentityNameResolver(CordaClient client) {
this.cnName2shortHash = setCnName2shortHash(client);
}
private static Map<String, String> setCnName2shortHash(CordaClient client) {
Map<String, String> map = new HashMap<>();
List<virtualNodes> vNodesList = client.getVirtualnode();
try {
for (virtualNodes vNode : vNodesList) {
var identity = vNode.holdingIdentity();
if (identity.isPlayer()) {
map.put(identity.getName().toLowerCase(locale), identity.shortHash());
}
}
} catch (InvalidNameException e) {
// TODO: logs
System.out.println("Unable to get ShorHash map for Corda virtual nodes: "+e.getExplanation());
e.printStackTrace();
}
System.out.println("ApiUserShortHashMap " + map);
return map;
}
String getShortHashBy(String apiUserName) {
return this.cnName2shortHash.get(apiUserName.toLowerCase(locale));
}
}

View File

@ -10,8 +10,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import djmil.cordacheckers.cordaclient.pojo.virtualNodes; import djmil.cordacheckers.cordaclient.dao.VirtualNode;
import djmil.cordacheckers.cordaclient.pojo.virtualnode; import djmil.cordacheckers.cordaclient.dao.VirtualNodes;
@Service @Service
public class CordaClient { public class CordaClient {
@ -25,20 +25,19 @@ public class CordaClient {
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
} }
public List<virtualNodes> getVirtualnode() { public List<VirtualNode> getVirtualNodeList() {
// Request authorization header // Request authorization header
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = basicAuthorizationHeader();
headers.setBasicAuth("admin", "admin");
// Request // Request
final HttpEntity<String> request = new HttpEntity<>(headers); final HttpEntity<String> request = new HttpEntity<>(headers);
ResponseEntity<virtualnode> resp = this.restTemplate.exchange( ResponseEntity<VirtualNodes> resp = this.restTemplate.exchange(
"https://localhost:8888/api/v1/virtualnode", "https://localhost:8888/api/v1/virtualnode",
HttpMethod.GET, HttpMethod.GET,
request, request,
virtualnode.class ); VirtualNodes.class );
// TODO: throw exeption instead // TODO: throw exeption instead
if (resp.getStatusCode() != HttpStatus.OK || !resp.hasBody()) { if (resp.getStatusCode() != HttpStatus.OK || !resp.hasBody()) {
@ -47,4 +46,20 @@ public class CordaClient {
return resp.getBody().virtualNodes(); return resp.getBody().virtualNodes();
} }
// public String getGemeProposals(String ) {
// // Request authorization header
// HttpHeaders headers = basicAuthorizationHeader();
// // Request
// final HttpEntity<String> request = new HttpEntity<>(headers);
// }
private HttpHeaders basicAuthorizationHeader() {
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth("admin", "admin");
return headers;
}
} }

View File

@ -0,0 +1,63 @@
package djmil.cordacheckers.cordaclient;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.naming.InvalidNameException;
import org.springframework.stereotype.Service;
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
@Service
public class HoldingIdentityResolver {
static final Locale locale = Locale.getDefault();
/*
* NOTE: Proof-of-Concept impementation
* Ideally, we want to be able to update HoldingIdentity cache, had the
* corda cluster configuration changed (aka in case of a cache miss)
*/
final Map<String, HoldingIdentity> cache;
HoldingIdentityResolver(CordaClient cordaClient) {
this.cache = setCache(cordaClient);
}
private static Map<String, HoldingIdentity> setCache(CordaClient cordaClient) {
Map<String, HoldingIdentity> map = new HashMap<>();
List<VirtualNode> vNodeList = cordaClient.getVirtualNodeList();
try {
for (VirtualNode vNode : vNodeList) {
var identity = vNode.holdingIdentity();
if (identity.isPlayer()) {
map.put(identity.getName().toLowerCase(locale), identity);
}
}
} catch (InvalidNameException e) {
// TODO: logs
System.out.println("Unable to get ShorHash map for Corda virtual nodes: "+e.getExplanation());
e.printStackTrace();
}
return map;
}
/*
* @param apiUserName
* HoldingIdentity x500 name typically looks like
* "CN=Bob, OU=Player, O=Checkers, L=Kviv, C=UA"
* CN - is a common name, expected to be unique for CordaCheckers setup.
*
* @return HoldingIdentity
*/
public HoldingIdentity getByCommonName(String apiUserName) {
return this.cache.get(apiUserName.toLowerCase(locale));
}
}

View File

@ -1,4 +1,6 @@
package djmil.cordacheckers.cordaclient.pojo; package djmil.cordacheckers.cordaclient.dao;
import java.io.Serializable;
import javax.naming.InvalidNameException; import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName; import javax.naming.ldap.LdapName;
@ -7,7 +9,7 @@ import javax.naming.ldap.Rdn;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public record holdingIdentity(String x500Name, String shortHash) { public record HoldingIdentity(String x500Name, String shortHash) implements Serializable {
public String getName() throws InvalidNameException { public String getName() throws InvalidNameException {
LdapName ln = new LdapName(x500Name); LdapName ln = new LdapName(x500Name);
@ -18,7 +20,7 @@ public record holdingIdentity(String x500Name, String shortHash) {
} }
} }
throw new IllegalArgumentException("CN was not found"); throw new IllegalArgumentException("CN not found: "+ x500Name);
} }
public boolean isPlayer() throws InvalidNameException { public boolean isPlayer() throws InvalidNameException {

View File

@ -0,0 +1,6 @@
package djmil.cordacheckers.cordaclient.dao;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public record VirtualNode(HoldingIdentity holdingIdentity) { }

View File

@ -1,8 +1,8 @@
package djmil.cordacheckers.cordaclient.pojo; package djmil.cordacheckers.cordaclient.dao;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public record virtualnode(List<virtualNodes> virtualNodes) { } public record VirtualNodes(List<VirtualNode> virtualNodes) { }

View File

@ -1,6 +0,0 @@
package djmil.cordacheckers.cordaclient.pojo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public record virtualNodes(holdingIdentity holdingIdentity) { }

View File

@ -6,8 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import djmil.cordacheckers.cordaclient.CordaClient; import djmil.cordacheckers.cordaclient.CordaClient;
import djmil.cordacheckers.cordaclient.pojo.holdingIdentity; import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import djmil.cordacheckers.cordaclient.pojo.virtualNodes; import djmil.cordacheckers.cordaclient.dao.VirtualNode;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -24,10 +24,10 @@ public class CordaClientTest {
@Test @Test
void whenGetVirtualnode_thenListHoldingIdentity() throws GeneralSecurityException, IOException, InvalidNameException { void whenGetVirtualnode_thenListHoldingIdentity() throws GeneralSecurityException, IOException, InvalidNameException {
List<virtualNodes> vNodes = cordaclient.getVirtualnode(); List<VirtualNode> vNodes = cordaclient.getVirtualNodeList();
holdingIdentity identity = vNodes.get(0).holdingIdentity(); HoldingIdentity identity = vNodes.get(0).holdingIdentity();
assertThat(identity.getName()).isEqualTo("NotaryRep1"); assertThat(identity.getName()).isEqualTo("Bob");
} }
} }

View File

@ -27,8 +27,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import djmil.cordacheckers.cordaclient.pojo.holdingIdentity; import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import djmil.cordacheckers.cordaclient.pojo.virtualnode; import djmil.cordacheckers.cordaclient.dao.VirtualNodes;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -80,19 +80,19 @@ class CordacheckersApplicationTests {
// Request // Request
final HttpEntity<String> request = new HttpEntity<>(headers); final HttpEntity<String> request = new HttpEntity<>(headers);
final ResponseEntity<virtualnode> response = new RestTemplate(requestFactory) final ResponseEntity<VirtualNodes> response = new RestTemplate(requestFactory)
.exchange("https://localhost:8888/api/v1/virtualnode", HttpMethod.GET, request, virtualnode.class); .exchange("https://localhost:8888/api/v1/virtualnode", HttpMethod.GET, request, VirtualNodes.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.hasBody()); assertThat(response.hasBody());
virtualnode vNode = response.getBody(); VirtualNodes vNode = response.getBody();
assertThat(vNode).isNotNull(); assertThat(vNode).isNotNull();
if (vNode != null) { if (vNode != null) {
assertThat(vNode.virtualNodes().size() == 5); assertThat(vNode.virtualNodes().size() == 5);
} }
holdingIdentity identity = vNode.virtualNodes().get(0).holdingIdentity(); HoldingIdentity identity = vNode.virtualNodes().get(0).holdingIdentity();
assertThat(identity.x500Name().contains("NotaryRep1")); assertThat(identity.x500Name().contains("NotaryRep1"));
} }
} }

View File

@ -2,7 +2,7 @@ package djmil.cordacheckers;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import djmil.cordacheckers.cordaclient.pojo.holdingIdentity; import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -14,8 +14,8 @@ public class HoldingIdentityTest {
@Test @Test
public void isPlayerTest() throws InvalidNameException { public void isPlayerTest() throws InvalidNameException {
holdingIdentity alice = new holdingIdentity("CN=Alice, OU=Player, O=Checkers, L=Zug, C=CH", "HHHDDD"); HoldingIdentity alice = new HoldingIdentity("CN=Alice, OU=Player, O=Checkers, L=Zug, C=CH", "HHHDDD");
holdingIdentity bob = new holdingIdentity("CN=Bob, OU=Other, O=Checkers, L=Zug, C=CH", "HHHDDD"); HoldingIdentity bob = new HoldingIdentity("CN=Bob, OU=Other, O=Checkers, L=Zug, C=CH", "HHHDDD");
assertThat(alice.getName()).isEqualTo("Alice"); assertThat(alice.getName()).isEqualTo("Alice");
assertThat(alice.isPlayer()).isEqualTo(true); assertThat(alice.isPlayer()).isEqualTo(true);