-
Notifications
You must be signed in to change notification settings - Fork 390
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
Add factories as an extension mechanism for Auth #352
Conversation
Hmmm... those tests aren't failing for me in Eclipse even if I'm using Java 8. I suspect it's due to compiler differences. I will have a look. |
@pitbulk Hi Sixto, did you have a change to look at this? |
Can you move all the factories inside its own folder? In the second approach, maybe the new constructor can call the build method, so we keep it |
Done in 784e26a.
Yes, this is what I had in mind. So, let me implement it in that way. Coming back soon :-) |
Ok @pitbulk, here it is the API enhancement for In the end, I did not make the new Please note two things:
// null is the HTTP request - it's not needed to build an outgoing LogoutResponse
LogoutResponse response = new LogoutResponse(settings, null);
response.build(); Now calling the LogoutResponse response = new LogoutResponse(settings, (HttpRequest) null);
response.build(); or replaced with the new recommended constructor: LogoutResponse response = new LogoutResponse(settings, new LogoutResponseParams());
// no need to call build() any longer I'm not sure what happens at runtime with precompiled consumers: I think that the binding to the I think this is an acceptable compromise, especially because I expect most consumers just use the |
I just verified that binary compatibility is ok. To test this, I did the following:
Saml2Settings settings = new Saml2Settings();
LogoutResponse r = new LogoutResponse(settings, null);
r.build();
System.out.println(DateTimeFormatter.ISO_INSTANT.format(r.getIssueInstant().getTime().toInstant()));
So, as I expected, binary compatibility is maintained: consumer code that needs to be recompiled just needs to fix the mentioned compilation error with one of the solutions I described in my previous comment. |
Now devs using the Auth object will see 6 new methods that don't gonna need if they are using the toolkit as it is. I wonder if there is any other way to allow to extend the auth class, without adding all such amount of methods and new attributes:
Maybe a hash attribute containing all the default factories? |
Well, the above default implementations and corresponding fields are not actually visible to toolkit consumers, as they are private implementation details. I decided to declare the default implementations as static constants because so I can use them as a safe default in case someone sets There are 6 of them because, indeed, there are 6 cases to handle in the worst case scenario (that is: you need to cusomise ALL the messages): AuthnRequest+Response, +2 LogoutRequest and +2 LogoutResponse scenarios (incoming/outgoing, depending on whether the logout process is SP-initiated or IdP-initiated). Indeed, the only changes that consumers will see are 6 new setters, which can be safely ignored if they just want to use the default implementation like they do right now. As an alternative approach we may provide a factory that is able to create all message instances. Let's call it This may reduce the code in Auth auth = new Auth(request, response);
auth.setAuthnRequestFactory(MyOwnAuthnRequest::new);
auth.setSamlResponseFactory(MyOwnSamlResponse::new);
auth.login(); With the alternative approach I need to:
public class MySamlMessageFactory implements SamlMessageFactory {
@Override
public AuthnRequest createAuthnRequest(Saml2Settings settings, AuthnRequestParams params) {
return new MyOwnAuthnRequest(settings, params);
}
@Override
public SamlResponse createSamlResponse(Saml2Settings settings, HttpRequest request) {
return new MyOwnSamlResponse(settings, request);
}
}
Auth auth = new Auth(request, response);
auth.setSamlMessageFactory(new MySamlMessageFactory());
auth.login(); I save one setter call on Some other alternatives? Just one setter on After all I am still personally preferring the current approach. |
A developer that doesn't need to extend the classes at all will be confused to see those new 6 methods, that my opinion. I believe that someone extending the current classes and adding already its own code, will be ok with the extra effort required by the SamlMessageFactory approach. The "map of factory instances" seems also an acceptable approach for me. So, I basically prioritize the usability of the majority of developers, that don't gonna extend the classses, while allowing others to extend the classes, but not maybe in the easiest form for them, but they will already touch the code, and know about SAML since they are extending the classes, so I believe its ok. |
If you believe that the number of SAML messages actually handled by the |
Ok, so I will explore this path. I personally prefer this much more than the map approach... |
One of the main goals of the OneLogin SAML toolkits is to keep them simple. There are already other implementations covering the whole SAML spec, but its settings and code are kinda complex... so our goaI is to offer a simple implementation that follows the standard and allows you to cover the majority of use cases. That said, we are open to allow others to build more complex implementations using our project as a base, and here is where the factory extension appears, but we need to keep the focus on simplicity. |
Simplicity is the reason why I chose java-saml myself, so I agree with this :-) Indeed, in all my PRs I strive to maintain it as simple and backward compatible as possible, while still allowing it to be reasonably powerful when needed. I will get back soon on this one. |
Yes, I appreciate your huge effort in the contributions you did. |
8d14312
to
a7d45cd
Compare
Here it is @pitbulk. Let me know what you think. Maybe I can try to squash some commits, if you prefer to simplify the history. The only problem is that it's probably worth to keep some of the above commits on their own and there's a bit of overlapping between the first approach implementation and, for instance, the change in LogoutResponse API. |
This allows library consumers to extend the standard java-saml message classes with custom implementations while still using the built-in Auth class to orchestrate the message flow, by providing a mechanism to plug-in custom object creation logic.
It seems like neither Eclipse ecj nor javac in Java 11 complains when using constructor references in these very special cases used for unit testing. But javac in Java 8 does. So, we're now using lambda expressions in place of constructor references: this seems to make all compilers happy.
This introduces LogoutResponseParams and allows to make the whole API coherent when building outgoing SAML messages. The Auth factories benefit from this as well, because they now share a common construction and usage pattern.
These details were overlooked in the first place: getters of the input params should better be public and fields can be declared as final. The useless NameId setter in LogoutRequestParams was temporarily introduced during development but should have been reverted from the beginning, so it's gone now. Some tests were improved to provide more accurate assertions.
In this way the API of Auth gets simplified.
a7d45cd
to
2917a41
Compare
@pitbulk do you think this can be merged in its current shape? :-) |
As anticipated in #350 and in #265 (comment), this is my proposal to enhance the
Auth
class to make it use custom extensions of the standard SAML message classes (AuthnRequest
,SamlResponse
,LogoutRequest
andLogoutResponse
) when needed, by introducing specialized factories for the purpose.The change is 100% backward compatible: when no factory is explicitly set on
Auth
, default factory instances are used such that instances of the default toolkit classes will be used, just as before.The changes introduced in #350 make this work quite well because now the parameters that drive the behaviour of the outgoing message classes are encapsulated into input param objects that can be passed to the factories and the whole APIs is quite consistent.
The only part where there's some discrepancy is the case of the creation of an outgoing
LogoutResponse
object. This is because there's no "LogoutResponseParams" class and such a logout response object is not built on construction (like it happens for all the other outgoing messages, likeAuthnRequest
orLogoutRequest
for the outgoing case), but withLogoutResponse.build()
. This complicates specifying a proper factory a little bit for just that case, see here:To improve that specific case, we may either:
SamlOutgoingMessageFactory
: this just moves the asymmetry, yet improving the java-saml consumer experience in case a custom outgoing logout response implementation is neededLogoutResponseParams
object; this may carry on information like theinResponseTo
and theSamlResponseStatus
/statusCode
LogoutResponse
constructor overloading that takes settings and aLogoutResponseParams
as input; this overloading would cover the "outgoing" case and would do whatLogoutResponse.build()
currently does; indeed, in this case theHttpRequest
parameter of the only constructor we currently have is useless; there would be a symmetry with the differentLogoutRequest
constructors, one of which is used to cover the "incoming" case, the other one for the "ougoing" case insteadbuild()
(it becomes useless and even detrimental if inadvertitedly used when theLogoutResponse
is received), but keep it just to maintain backward compatibilitySamlOutgoingMessageFactory<LogoutResponseParams, LogoutResponse>
and it could be set with a straightforward:Of course, the second solution is much better and improves the API coherence. If you agree with this approach, I may integrate it in this PR.