-
Notifications
You must be signed in to change notification settings - Fork 18
SDK-2771: Add new maven module for supporting the creation of Yoti au… #505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: DEVELOPMENT
Are you sure you want to change the base?
Conversation
…thentication tokens via API
pn-santos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not that familiar with the current practices in the SDK nor any stylistic conventions in place so apologies in advance if any of the comments are out-of-sync with how stuff is done in the SDKs.
Also, given I'm not really involved with the SDKs don't take the comments as blockers. I'm not going to explicitly approve, I'll leave that to someone more attuned to the SDK code base to take that responsibility, but I don't see anything functional that would be a reason to hold it up.
| public CreateAuthenticationTokenResponse generate(List<String> scopes) throws ResourceException, IOException { | ||
| String jwts = signedJwtGenerator.create(sdkId, keyPair, jwtIdSupplier, authApiUrl); | ||
|
|
||
| Map<String, String> formParams = new HashMap<>(DEFAULT_FORM_VALUES); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it really worth having a constant for DEFAULT_FORM_VALUES? It's only used here, why not inline the put here?
formParams.put("scope", String.join(" ", scopes));
formParams.put("client_assertion", jwts);
formParams.put("grant_type", "client_credentials");
formParams.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");it's one less map instance to keep/copy and initialise on startup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
| private final SignedJwtGenerator signedJwtGenerator; | ||
|
|
||
| private final String authApiUrl; | ||
| private final ObjectMapper objectMapper; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the ObjectMapper being injected/exposed?
The CreateAuthenticationTokenResponse is defined by this module, shouldn't the ObjectMapper (and it's config) also be owned/defined by this module? What would be the point of the client providing their own instance config if they do not control the annotations/layout of CreateAuthenticationTokenResponse?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't expose the ObjectMapper to the Relying Business, but it's done this way (and in other places in the SDK) to make testing with mocks easier in the AuthenticationTokenGenerator
| private final FormRequestClient formRequestClient; | ||
| private final SignedJwtGenerator signedJwtGenerator; | ||
|
|
||
| private final String authApiUrl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this be better as a URI or URL type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
| ObjectMapper objectMapper = new ObjectMapper() | ||
| .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) | ||
| .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do not expose the ObjectMapper then there's no point in making it an instance var. It should be declared as a static final var of AuthenticationTokenGenerator
| .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) | ||
| .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | ||
|
|
||
| return new AuthenticationTokenGenerator(sdkId, loadKeyPair(keyPairSource), jwtIdSupplier, new FormRequestClient(), new SignedJwtGenerator(), objectMapper); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not all that familiar with the current conventions in this project, but I feel like with this many arguments this would be a bit more readable with one arg per line:
| return new AuthenticationTokenGenerator(sdkId, loadKeyPair(keyPairSource), jwtIdSupplier, new FormRequestClient(), new SignedJwtGenerator(), objectMapper); | |
| return new AuthenticationTokenGenerator( | |
| sdkId, | |
| loadKeyPair(keyPairSource), | |
| jwtIdSupplier, | |
| new FormRequestClient(), | |
| new SignedJwtGenerator(), | |
| objectMapper | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
| String urlParams = getDataString(formParams); | ||
|
|
||
| byte[] postData = urlParams.getBytes(StandardCharsets.UTF_8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why return String and then do getBytes(...) rather than simply return byte[] from the get go? urlParams doesn't seem to be used anywhere else and what this (method) seems to need is the byte[] not String
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
| String responseBody = formRequestClient.performRequest(authApiUrl, "POST", formParams); | ||
|
|
||
| return objectMapper.readValue(responseBody, CreateAuthenticationTokenResponse.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If all we are doing is de-serializing the response, why return String instead of the raw byte[]?
This ends up creating String instances from byte[] just to immediately convert them to a POJO instance. We could go directly from byte[] to POJO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
| return scanner.get().hasNext() ? scanner.get().next() : ""; | ||
| } | ||
| } | ||
|
|
||
| private static Scanner createScanner(InputStream inputStream) { | ||
| return new Scanner(inputStream, DEFAULT_CHARSET).useDelimiter("\\A"); | ||
| } | ||
|
|
||
| private byte[] readBody(HttpURLConnection httpURLConnection) throws IOException { | ||
| try (QuietCloseable<InputStream> inputStream = new QuietCloseable<>(httpURLConnection.getInputStream())) { | ||
| return readChunked(inputStream.get()); | ||
| } | ||
| } | ||
|
|
||
| private byte[] readChunked(InputStream inputStream) throws IOException { | ||
| ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); | ||
| byte[] byteChunk = new byte[4096]; | ||
| int n; | ||
| while ((n = inputStream.read(byteChunk)) > 0) { | ||
| byteArrayOutputStream.write(byteChunk, 0, n); | ||
| } | ||
| return byteArrayOutputStream.toByteArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the difference in strategy for reading error payloads and non-error payloads?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was taken from one of our other (old) components from the regular SDK module. I have fixed it 👍
| this.signedJwtGenerator = signedJwtGenerator; | ||
| this.objectMapper = objectMapper; | ||
|
|
||
| authApiUrl = System.getProperty(Properties.PROPERTY_YOTI_AUTH_URL, Properties.DEFAULT_YOTI_AUTH_URL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is the sole user of this Properties class... why not simply declare those values as constants here? i.e.
private static final String YOTI_AUTH_URL_KEY = "yoti.auth.url";
(...)
authApiUrl = System.getProperty(YOTI_AUTH_URL_KEY, "https://auth.api.yoti.com/v1/oauth/token");and get rid of Properties altogether?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have public properties in here, so the YOTI_AUTH_URL_KEY property can be accessed if we want to override the URL without knowing the actual key needed (like we do in the other modules).
Not sure if we want to change this, @bucky-boy what do you think?
| final class SignedJwtGenerator { | ||
|
|
||
| String create(String sdkId, KeyPair keyPair, Supplier<String> jwtIdSupplier, String authApiUrl) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems kinda pointless to have a class that doesn't implement an interface, doesn't have instance member variables, and has a single method that is used in one place. Why not simply inline the method where it's used as a private method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍 (this was originally done to make mocking easier, but I have since found we have utility libraries to help us create actual KeyPairs)
| String jwts = signedJwtGenerator.create(sdkId, keyPair, jwtIdSupplier, authApiUrl); | ||
|
|
||
| Map<String, String> formParams = new HashMap<>(DEFAULT_FORM_VALUES); | ||
| formParams.put("scope", String.join(" ", scopes)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this list be checked for null or empty string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
| * @throws IOException | ||
| */ | ||
| public CreateAuthenticationTokenResponse generate(List<String> scopes) throws ResourceException, IOException { | ||
| String jwts = signedJwtGenerator.create(sdkId, keyPair, jwtIdSupplier, authApiUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't this be inlined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
|



…thentication tokens via API