-
Notifications
You must be signed in to change notification settings - Fork 45
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 multiple role support #119
Conversation
The `https://aws.amazon.com/SAML/Attributes/Role` attribute supports single or multiple values. This fixes Awsaml to work when a multiple-value attribute is passed. Currently, the first role is always used. Support for selecting from the roles may be added later. GH-105
This appears to not be needed and gets in the way when using react dev mode.
This adds a role switcher UI when multiple roles,saml-provider pairs are included in the SAML assertion. If only one role is present in the SAML assertion, the switcher is not displayed. GH-105
|
||
if (status === 'selected') { | ||
return <Redirect to="/refresh" />; | ||
} |
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 this the correct way to navigate to a different page after an action? This navigates to the refresh page after selecting a role. Doing this from a render method didn't feel natural to me. Is there a better way?
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 is how react-router works, to redirect you render the redirect component. 🤷♂️
To avoid clutter, the profile name is only displayed if it has been customized.
@@ -43,6 +43,7 @@ class Configure extends Component { | |||
this.state = { | |||
auth: (params['?auth'] && params['?auth'] === 'true'), | |||
loaded: false, | |||
selectRole: (params['?select-role'] && params['?select-role'] === 'true'), |
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 possible for select-role
to be passed as a query parameter when auth
is passed? assuming not 👍
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.
Not currently.
} | ||
|
||
return ( | ||
<RenderIfLoaded isLoaded={this.state.loaded}> |
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.
yikes, we should look at having skeleton content (instead of rendering based on async wait), but this is 👌 based on current setup.
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 few comments I had around formatting components can be addressed with the following patches:
diff --git i/src/containers/refresh/Credentials.js w/src/containers/refresh/Credentials.js
index b9263fa..dbf2c20 100644
--- i/src/containers/refresh/Credentials.js
+++ w/src/containers/refresh/Credentials.js
@@ -16,13 +16,19 @@ const CredProps = styled.dl`
const CredPropsKey = styled.dt`
grid-column: 1;
- margin-right: .5rem;
+ margin-right: 1rem;
+ margin-bottom: 10px;
+ line-height: 2.5rem;
`;
const CredPropsVal = styled.dd`
grid-column: 2;
`;
+const SmallMarginCardBody = styled(CardBody)`
+ padding: 1.25rem 0.5rem;
+`;
+
export const Credentials = ({awsAccessKey, awsSecretKey, awsSessionToken}) => {
const creds = new Map();
@@ -42,7 +48,7 @@ export const Credentials = ({awsAccessKey, awsSecretKey, awsSessionToken}) => {
<details>
<summary>Credentials</summary>
<Card>
- <CardBody className="bg-light">
+ <SmallMarginCardBody className="bg-light">
<CredProps>
{
Array.from(creds).map(([name, value]) => {
@@ -61,7 +67,7 @@ export const Credentials = ({awsAccessKey, awsSecretKey, awsSessionToken}) => {
})
}
</CredProps>
- </CardBody>
+ </SmallMarginCardBody>
</Card>
</details>
);
diff --git i/src/containers/refresh/Refresh.js w/src/containers/refresh/Refresh.js
index f583afd..c42126f 100644
--- i/src/containers/refresh/Refresh.js
+++ w/src/containers/refresh/Refresh.js
@@ -39,6 +39,8 @@ const AccountProps = styled.dl`
grid-template-columns: auto 1fr;
margin: 0;
padding: .5rem;
+ font-family: Consolas,monospace;
+ font-size: 1rem;
`;
const AccountPropsKey = styled.dt`
api/routes/auth.js
Outdated
roleAttr = [roleAttr]; | ||
} | ||
|
||
let roles = roleAttr.map((attr, i) => { |
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 should be a const
. let
is mutable.
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.
Looking a little further down you could even just set it to session.roles
and key off that value when you check the length.
api/routes/auth.js
Outdated
} | ||
|
||
let roles = roleAttr.map((attr, i) => { | ||
let arns = attr.split(','); |
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.
const
as well.
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.
Do we want to gracefully handle a case in which the value from the https://aws.amazon.com/SAML/Attributes/Role
attribute isn't formatted like:
arn:aws:iam:role1,arn:aws:iam:provider
arn:aws:iam:role2,arn:aws:iam:provider
arn:aws:iam:role3,arn:aws:iam:provider
If so, do you think it makes sense to introduce some sort of arn parsing mechanism so that you can definitively know if any value in that attribute is formatted correctly and, if not, throw it away?
api/routes/auth.js
Outdated
let arns = attr.split(','); | ||
|
||
return { | ||
accountId: arns[0].split(':')[4], |
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 look pretty brittle. Any way you can add some validation to make sure it doesn't return undefined
if the arns
value isn't structured correctly?
api/routes/auth.js
Outdated
index: i, | ||
principalArn: arns[1], | ||
roleArn: arns[0], | ||
roleName: arns[0].split(':')[5].replace('role/', ''), |
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.
Same as above re: brittle-ness.
As a suggestion, consider something like
const [roleArn, principalArn] = arns;
const roleArnArray = roleArn.split(':');
const accountId = roleArnArray[4];
const roleName = roleArnArray[5];
return {
accountId,
index: i,
principalArn,
roleArn,
roleName,
};
api/routes/auth.js
Outdated
// the latest roles from the current SAML assertion. If it | ||
// doesn't match, wipe it from the session. | ||
if (session.roleArn && session.principalArn) { | ||
let found = roles.find((role) => |
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.
const
src/containers/refresh/Refresh.js
Outdated
<pre className={getLang(platform)}> | ||
{getEnvVars(this.props)} | ||
</pre> | ||
<InputGroupWithCopyButton |
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.
Love that you got the copy button working here but is there a way to maintain the pre
formatting?
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.
What formatting are you looking for. Are you actually looking for wrapping this text in <pre></pre>
? Or are you looking for dark background and monospace font?
The current copy/paste stuff works with <textarea>
, not <pre>
so I don't think I can (easily) switch to pre. But I can get the dark background and monospace font if that is the goal.
<pre className="card-text language-markup"> | ||
<code>{accountId}</code> | ||
</pre> | ||
<AccountProps className="bg-dark text-light"> |
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 there a way to maintain the pre
formatting here?
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.
Not technically with <pre>
since I'm now laying this out with a grid layout. Personally I prefer non-monospaced font here since this isn't code or anything like that. I'm leaving as is. Hope that is ok.
src/containers/select-role/Role.js
Outdated
submitSelectRole: bindActionCreators(submitSelectRole, dispatch), | ||
}); | ||
|
||
export const Role = connect(mapStateToProps, mapDispatchToProps)(RoleComponent); |
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 you don't need mapStateToProps
you can just pass null
to the connect
function instead of a function that does nothing.
api/routes/select-role.js
Outdated
router.get('/', (req, res) => { | ||
const session = req.session.passport; | ||
|
||
if (session === undefined) { |
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 can be if (!session) {
|
||
const axiosClient = axios.create({ | ||
baseURL: ENDPOINTS.electron, |
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.
Will this still work correctly without the baseURL
option pointing to the backend? If I remember correctly I had some issues with it looking like it worked when testing but it failing using a built version.
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.
In a built version, everything is port 2600 so it works. I've tested this.
I may be doing it wrong, but in a dev environment I run these two commands in separate terminals:
yarn electron-dev
(runs on port 2600)yarn react-start
(runs on port 3000, and proxies non-react stuff to port 2600)
In this case, everything should go through port 3000. Previously, some stuff was going to port 2600 and some to port 3000 and this messed up some CORS stuff (I think).
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.
Definitely worth a shot. If we build and release and see bugs here we can always roll this back.
This uses the new profile UUID when editing profile names instead of just the metadata URL.
req.body.index can be 0, so we need to explictly check for undefined.
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.
Looks good. My feedback is mostly stylistic at this point and revolves around using forEach
vs map
.
api/routes/configure.js
Outdated
if (profileName && storedMetadataUrl.url === metadataUrl && storedMetadataUrl.name !== profileName) { | ||
storedMetadataUrl.name = profileName; | ||
if (profile.url !== metadataUrl) { | ||
return res.status(422).json(Object.assign({}, ResponseObj, { |
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.
Do we want to return an error here or update the profile.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.
My thoughts are this should be an impossible state to get to. When you press the "Login" button now, it submits the metadata URL and the UUID for the profile. The metadata url box is not editable in the current UI. If the submitted metadata URL doesn't match the stored metadata URL (found using the uuid) then I assume corrupt input and throw an error.
If we ever update the UI to allow changing the metadata URL for existing entries, we can change this.
api/routes/refresh.js
Outdated
|
||
return storedMetadataUrl; | ||
// Update the stored profile with account number(s) and profile names | ||
metadataUrls.forEach((metadata) => { |
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 a huge fan of using forEach
as it tends to lead to mutation and side-effects. However, I recognize that's a stylistic issue and just something I want to call out here for future reference.
|
||
const axiosClient = axios.create({ | ||
baseURL: ENDPOINTS.electron, |
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.
Definitely worth a shot. If we build and release and see bugs here we can always roll this back.
This adds a role switcher when multiple roles are passed via the SAML assertion. This is similar to how the AWS console handles multiple roles. When a single role is passed, the behavior is unchanged.
This also includes a few random fixes/improvements that I found along the way:
Note to reviewers:
I am not a react or express developer, much less a react+redux+express+electron developer. Please keep this in mind when reviewing and feel free to suggest stylistic changes.
Note to Okta users:
AWS requires that multiple roles are passed as multiple values to the
https://aws.amazon.com/SAML/Attributes/Role
attribute (see the AWS docs). By default, Okta passes multiple values as a single value separated by commas. To fix this, you must contact Okta support and ask them to enable theSAML_SUPPORT_ARRAY_ATTRIBUTES
feature flag. See this post.Once this feature flag is enabled, you can add specify multiple roles using an Okta expression: