-
Notifications
You must be signed in to change notification settings - Fork 475
Require cert for every strategy #548
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
Conversation
@markstos, @gugu, @srd90, @HendrikJan: Did you want to have a look at this before I fix all the tests to include a cert? |
@cjbarth Seems like a good path to me. Thanks for all the work. s/manditory/mandatory/g |
Are you sure change of casing needed? It will start to silently ignore these parameters in existing JS code, which will lead to unexpected behavior changing in module users' code |
It isn't needed, but it does unify our casing. We had a mix of capitalizing acronyms entirely and only capitalizing the first letter. I have followed the .Net convention here (which is very close to TypeScript in most ways): https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions?redirectedfrom=MSDN. You might also see this short, reasonable post: https://www.approxion.com/capital-offenses-how-to-handle-abbreviations-in-camelcase/ Thus, in a variable name "SAML" is "Saml" and "RAC" is "Rac". There are already many things in this version that are breaking changes so it seems like a good time to do this. If there are other things of this nature, I don't mind a branch off the 2.x tag and putting in a few more deprecation warnings for things we know are breaking in this code. |
@cjbarth Due to my limited typescript/javascript knowledge and quite large changes in this pull request (which include from my point of view ts/js specific "magic") I'm unable to see just by browsing the code whether |
I'm missing time and experience to fully review these changes. |
@srd90 I appreciate your feedback. I updated the code in my latest commit to do better checking of the cert property to check for each member of an array. This function is the only one that reads I will add more test to exercise the two new errors that this |
A lot of tests are fixed. There is one test that wasn't hermetically sealed. There are other tests that are now failing because they aren't signed, so providing a fake cert doesn't help them pass.
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've reviewed my own code and I think we're good to go on this. Of course, feedback is welcome.
I expect that there is more work that can be done to strengthen the types and to add tests to make sure that we have better coverage, especially of the code that throws exceptions. However, at this point, I believe this will cover cert
being required. Of course, this is a breaking change.
I will create a follow-up PR to remove support for privateCert
, since it is deprecated anyway.
@gugu Are there any more changes you'd like to suggest on this? @HendrikJan or @srd90 Do you have any thoughts on this? |
No, that's all |
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 made a few generic comments, I don't know the code base too well yet, so it's hard for me to comment on the code organization.
One thing I noticed it that this PR contains quite a lot of lint fixes (which is great, for example '!=
=> !==
) and caml case fixes (RAC => Rac) which is also nice, but I would strongly suggest to make a dedicated PR so it can be merged quickly, and then it's easier for everyone to focus on the actual changes (sorry, yet another comment that's a bit generic, that's all I have for now ;) )
this.options = options as CacheProviderOptions; | ||
this.options = { | ||
...options, | ||
keyExpirationPeriodMs: options.keyExpirationPeriodMs, |
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.
It's not clear to me what this line does
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.
It allows us to preserve all incoming options, while simultaneously converting the option from partial<CacheProviderOptions>
to CacheProviderOptions
.
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 see. I would find it clearer with
this.options = {
keyExpirationPeriodMs: 28800000; // 8 hours
...options
};
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've made an adjustment to clarify this code block. Please have a look at this change.
src/passport-saml/types.ts
Outdated
/** | ||
* These options are availble for configuring a SAML strategy | ||
*/ | ||
export type SamlConfig = Partial<SamlOptions> & MandatorySamlOptions; |
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 type lost the StrategyOptions part, is it intended?
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 see that you as re-adding the type in the constructor here https://github.com/node-saml/passport-saml/pull/548/files#diff-f795057e9c7334402f4693eabcd8786ff52e2f791bf985c10d9acc98a57682aeR23
On the other hand for mutlisaml, you do include it in the type https://github.com/node-saml/passport-saml/pull/548/files#diff-c2e9d28b65918a1bdc8038b9c8dd097a2d455436b6a8855017aaf28ecda216a9R185
I feel it's not consistent. Also, I'm pretty sure that having the StrategyOptions type in the SamlConfig would avoid to have to many an inelegant explicit cast here https://github.com/node-saml/passport-saml/pull/548/files#diff-cb165d3df3a1dace61ca699c892c1408f9d89ebbe253c470a842b46f11e9150cR28
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.
The trick is that SamlConfig
requires a cert
, and MultiSamlConfig
does not. So, we can't pass a MultiSamlConfig
to the Strategy
ctor. What is more, getSamlOptions()
must return an object can can be used in the Stragegy
ctor, so it must include a cert
, whereas we don't need a cert
to construct MultiSamlStrategy
.
Having said all that, your original comment holds. Done.
VerifyWithoutRequest, | ||
VerifyWithRequest, | ||
} from "./types"; | ||
|
||
class MultiSamlStrategy extends SamlStrategy { | ||
_options: MultiSamlConfig; | ||
static newSamlProviderOnConstruct = 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.
I would find it more elegant to have it as a protected instance field. The current way forces an explicit cast, which should always be avoided IMO.
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.
And normally I would agree with you, but we can't have an instance field before the call to super
, and we need this information before doing that. I did however, mark it readonly
.
See "src/passport-saml/multiSamlStrategy.ts:23:3 - error TS2376: A 'super' call must be the first statement in the constructor when a class contains initialized properties, parameter properties, or private identifiers."
src/passport-saml/saml.ts
Outdated
* - better: Assertion context must be stronger than all contexts in the list | ||
*/ | ||
ctorOptions.RacComparison = ctorOptions.RacComparison ?? "exact"; | ||
if (!["exact", "minimum", "maximum", "better"].includes(ctorOptions.RacComparison)) { |
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 do the check in an IIFE ? I would find it clearer to have it after the object initialization
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.
The idea was to keep all the logic right together so there wouldn't be two places, even in the same ctor, to look for how a property ends up the way it is. However, I can see how IIFE would actually mutate the incoming obejct and make things slower. Done.
options.RACComparison = "exact"; | ||
} | ||
const options = { | ||
...ctorOptions, |
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 sure I understand everything that's going on.
Why not simply have something like:
const DEFAULTS = {
passive: false,
disableRequestedAuthnContext: false,
// all the default values
}
const options = {...DEFAULTS, ...ctorOptions};
Note that this can still cause problems with the user explicitly passes an option to null or undefined (like new SAML({passive: undefined})
) so some kind of cleanup might be needed beforehand (for JS users - TS users should not have this issue thanks to typing)
Something like
// remove all the undefined, and the null for non nullable options.
Object.keys(ctorOptions).forEach((key) => {
if(ctorOptions[key] === undefined) delete ctorOptions[key];
if(ctorOptions[key] === null && key !== 'identifierFormat' && key !== 'authnContext') delete ctorOptions[key];
})
Enumerating all the properties and their default with ??
as you did works too, but in this case I would not add the ...ctorOptions
at the beginning, which IMO is confusing (and maybe just explicit all properties that don't have any default, if any)
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.
Putting in ...ctorOptions
like is done is so that all properties don't have to be explicitly specified, only ones where there are some defaults or special handling.
Generally, I see what you are after here, but I feel like this logic might be easier to follow than the hoops you describe. It also makes it easier to extend in one place instead of having to go to multiple places. ("I have to remember that if null
is a valid value, to go to this other block to explicitly allow that.")
Just a thought: I think that rather than having MutliSaml being a special case of SingleSaml, you should actually have SingleSaml be a special case of MutliSaml (so reverse everything compared to how it's currently, have the main implementation be MultiSaml) That makes much more sense, since after all SingleSaml is just MutliSaml with n = 1 (so you could have getSamlOptions be a trivial function in this case, that alway return the same config) |
@forty Thank you very much for your review; I've incorporated several changes because of it. Please see my other comments on your review. As for the MultiSaml and Saml being the way it is, I'm sure you can imagine which one came first and why it is the way it is :) I am not opposed to reversing things, and would welcome a PR to help with that. I think that is out of scope for this PR though. We want to get this landed so we can stop holding up some other PRs. |
I'm going to land this tomorrow barring any additional feedback so that we can unblock a bunch of other PRs giving others a chance to work with the updated |
options.requestIdExpirationPeriodMs = 28800000; // 8 hours | ||
} | ||
|
||
if (!options.cacheProvider) { |
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.
Description
This addresses the issue that a SAML strategy could be constructed without a certificate, which would allow an insecure situation. The changes needed to force a cert were quite significant. Types had to be adjusted to accommodate these changes. Additionally, changes were made to tests to get them to fail with the correct error. Our tests were in a situation such that once a cert was required the tests would fail as expected, however, the error message that was shown in no way indicated what the reason for the failure actually was. The tests were updated only to get them to actually show the failure saying that "cert is required". This should yield a much better debugging experience going forward.
While the initial version of this PR has many tests failing, I wanted to get some feedback on the approach taken before I fix the tests by adding in a cert. I intend to add in the certs next week to complete this.
This PR also includes the test conditions that were mentioned in #523.
Checklist: