From 41af9de178c0db402d771eb2291f4ca935ec63ee Mon Sep 17 00:00:00 2001 From: jtsang01 Date: Mon, 15 Jan 2024 17:06:14 -0800 Subject: [PATCH 1/2] add support for gcp all and relaystate logic Signed-off-by: jtsang01 --- .../gcp/__snapshots__/login.test.js.snap | 7 ++++- ui/src/__tests__/pages/gcp/login.test.js | 4 ++- ui/src/pages/gcp/login.js | 27 ++++++++++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/ui/src/__tests__/pages/gcp/__snapshots__/login.test.js.snap b/ui/src/__tests__/pages/gcp/__snapshots__/login.test.js.snap index d01abd464a7..84e1ce24a08 100644 --- a/ui/src/__tests__/pages/gcp/__snapshots__/login.test.js.snap +++ b/ui/src/__tests__/pages/gcp/__snapshots__/login.test.js.snap @@ -182,7 +182,12 @@ exports[`GCP Login Page should render projects api returns results 1`] = ` value="" /> + diff --git a/ui/src/__tests__/pages/gcp/login.test.js b/ui/src/__tests__/pages/gcp/login.test.js index 6ba2ad26471..29a800d6c05 100644 --- a/ui/src/__tests__/pages/gcp/login.test.js +++ b/ui/src/__tests__/pages/gcp/login.test.js @@ -57,7 +57,9 @@ describe('GCP Login Page', () => { .mockReturnValue(Promise.resolve(testData)), }; MockApi.setMockApi(mockApi); - const { getByTestId } = renderWithRedux(); + const { getByTestId } = renderWithRedux(); await waitFor(() => expect(getByTestId('gcp-login')).toMatchSnapshot()); }); }); diff --git a/ui/src/pages/gcp/login.js b/ui/src/pages/gcp/login.js index ddab4ae6d05..4fdfd85cf09 100644 --- a/ui/src/pages/gcp/login.js +++ b/ui/src/pages/gcp/login.js @@ -81,16 +81,20 @@ const SubmitContainer = styled.div` display: flex; `; -function getAssertionsFromResourceAccessList(resourceAccessList, isAdmin) { +function getAssertionsFromResourceAccessList(resourceAccessList, userAuthority) { let assertionsList = []; if (resourceAccessList.resources) { resourceAccessList.resources.forEach(function (resources) { resources.assertions.forEach(function (assertion) { + if (userAuthority === 'all') { + assertionsList.push(assertion); + return; + } if (assertion.role.toLowerCase().indexOf('admin') > -1) { - if (isAdmin) { + if (userAuthority === 'admin') { assertionsList.push(assertion); } - } else if (!isAdmin) { + } else if (userAuthority === 'dev') { assertionsList.push(assertion); } }); @@ -139,6 +143,8 @@ export async function getServerSideProps(context) { let queryParams = context.query || {}; let isAdmin = queryParams.isAdmin === 'true'; + let userAuthority = queryParams.userAuthority || 'dev'; + let startPath = queryParams.startPath || ''; let projectDomainName = queryParams.projectDomainName || ''; let validationError = queryParams.validationError || ''; return { @@ -148,6 +154,8 @@ export async function getServerSideProps(context) { error, validationError, isAdmin, + startPath, + userAuthority, projectDomainName, _csrf: domains[0], }, @@ -199,10 +207,10 @@ class GCPLoginPage extends React.Component { } getAssertionList() { - const { resourceAccessList, isAdmin, projectDomainName } = this.props; + const { resourceAccessList, isAdmin, projectDomainName, userAuthority } = this.props; let assertionsList = getAssertionsFromResourceAccessList( resourceAccessList, - isAdmin + userAuthority ); // filter project list if projectDomainName is specified in the query if (projectDomainName) { @@ -312,8 +320,13 @@ class GCPLoginPage extends React.Component { > + Select a role: {displayProjects} From d6b5be7dfe29c4addf9c81da4d040a828dcbdcba Mon Sep 17 00:00:00 2001 From: jtsang01 Date: Wed, 17 Jan 2024 14:39:19 -0800 Subject: [PATCH 2/2] remove gcp related changes Signed-off-by: jtsang01 --- .../gcp/__snapshots__/login.test.js.snap | 241 ----------- ui/src/__tests__/pages/gcp/login.test.js | 65 --- ui/src/__tests__/server/handlers/api.test.js | 28 +- ui/src/pages/gcp/login.js | 382 ------------------ 4 files changed, 2 insertions(+), 714 deletions(-) delete mode 100644 ui/src/__tests__/pages/gcp/__snapshots__/login.test.js.snap delete mode 100644 ui/src/__tests__/pages/gcp/login.test.js delete mode 100644 ui/src/pages/gcp/login.js diff --git a/ui/src/__tests__/pages/gcp/__snapshots__/login.test.js.snap b/ui/src/__tests__/pages/gcp/__snapshots__/login.test.js.snap deleted file mode 100644 index 84e1ce24a08..00000000000 --- a/ui/src/__tests__/pages/gcp/__snapshots__/login.test.js.snap +++ /dev/null @@ -1,241 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GCP Login Page should render error no gcp project when api returns no results 1`] = ` -.emotion-0 { - border-bottom: 1px solid lightgray; - margin: 10px; - margin-bottom: 20px; -} - -.emotion-2 { - max-width: 800px; - margin: 0 auto; -} - -
-
- -
-
-

- Error: There are no GCP project roles associated with your account. -

-

- Check to make sure that your - - gcp.* - - roles contain your user. Your account should be in the configured federated roles that have access to the project. If that does not work, please ask your Athenz domain admin for assistance. -

-
-
-`; - -exports[`GCP Login Page should render projects api returns results 1`] = ` -.emotion-0 { - border-bottom: 1px solid lightgray; - margin: 10px; - margin-bottom: 20px; -} - -.emotion-0 { - border-bottom: 1px solid lightgray; - margin: 10px; - margin-bottom: 20px; -} - -.emotion-2 { - max-width: 800px; - margin: 0 auto; -} - -.emotion-2 { - max-width: 800px; - margin: 0 auto; -} - -.emotion-4 { - font-size: 16px; -} - -.emotion-6 { - font-size: 18px; - display: inline; - border-bottom: 1px solid lightgray; - margin-bottom: 10px; - padding: 10px; - width: 100%; - display: block; -} - -.emotion-8 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - margin-top: 10px; - margin-bottom: 10px; - margin-left: 20px; - font-weight: bold; -} - -.emotion-10 { - margin: 0; - font-weight: bold; - font-size: 16px; - color: #000; - vertical-align: middle; -} - -.emotion-12 { - vertical-align: middle; - width: 18px; - height: 18px; - margin: 10px; -} - -.emotion-14 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.emotion-16 { - border: none; - border-radius: 2px; - box-shadow: 0 0 0 1px transparent inset,0 0 0 0 #d5d5d5 inset; - box-sizing: border-box; - cursor: pointer; - display: inline-block; - font-family: Helvetica,Arial,sans-serif; - font-weight: 300; - font-size: 14px; - line-height: 1; - margin: 5px; - outline: 0; - padding: 10px 24px; - text-align: center; - text-shadow: none; - text-transform: none; - -webkit-transition: all 0.2s ease-in; - transition: all 0.2s ease-in; - vertical-align: baseline; - white-space: nowrap; - background: linear-gradient( - to right, - #3697f2, - #3570f4 - ); - color: #fff; -} - -.emotion-16:first-of-type { - margin-left: 0; -} - -.emotion-16:disabled { - background: rgba(48,48,48,0.1); - color: rgba(48,48,48,0.2); - cursor: not-allowed; -} - -.emotion-16:hover:not(:disabled) { - background: linear-gradient( - to right, - #3570f4, - #3448f7 - ); -} - -
-
- -
-
-
- - - -

- Select a role: -

-
-
- Project: - dummy-project-id - ( - dummy.project - ) -
-
-
- -
-
-
-
- -
-
-
-
-`; diff --git a/ui/src/__tests__/pages/gcp/login.test.js b/ui/src/__tests__/pages/gcp/login.test.js deleted file mode 100644 index 29a800d6c05..00000000000 --- a/ui/src/__tests__/pages/gcp/login.test.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright The Athenz Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { waitFor } from '@testing-library/react'; -import { renderWithRedux } from '../../../tests_utils/ComponentsTestUtils'; -import GCPLoginPage from '../../../pages/gcp/login'; -import MockApi from '../../../mock/MockApi'; - -afterEach(() => { - MockApi.cleanMockApi(); -}); - -describe('GCP Login Page', () => { - it('should render error no gcp project when api returns no results', async () => { - const mockApi = { - getResourceAccessList: jest.fn().mockReturnValue([]), - }; - MockApi.setMockApi(mockApi); - const { getByTestId } = renderWithRedux(, { - isFetching: false, - }); - await waitFor(() => - expect(getByTestId('gcp-login-error')).toMatchSnapshot() - ); - }); - - it('should render projects api returns results', async () => { - let testData = { - resources: [ - { - assertions: [ - { - role: 'dummy.project:role.user.dev', - resource: - 'projects/dummy-project-id/roles/dummy.role', - }, - ], - }, - ], - }; - const mockApi = { - getResourceAccessList: jest - .fn() - .mockReturnValue(Promise.resolve(testData)), - }; - MockApi.setMockApi(mockApi); - const { getByTestId } = renderWithRedux(); - await waitFor(() => expect(getByTestId('gcp-login')).toMatchSnapshot()); - }); -}); diff --git a/ui/src/__tests__/server/handlers/api.test.js b/ui/src/__tests__/server/handlers/api.test.js index 155d6ba005d..826c5925a64 100644 --- a/ui/src/__tests__/server/handlers/api.test.js +++ b/ui/src/__tests__/server/handlers/api.test.js @@ -307,20 +307,7 @@ describe('Fetchr Server API Test', () => { principal: 'user.dummy1', assertions: [ { - role: 'dummy.project:role.gcp.fed.power.user', - resource: - 'dummy.project:fed.power.user', - action: 'gcp.assume_role', - effect: 'ALLOW', - id: 1, - }, - { - role: 'dummy.project2:role.gcp.fed.admin.user', - resource: - 'dummy.project2:fed.admin.user', - action: 'gcp.assume_role', - effect: 'ALLOW', - id: 2, + dummyProperty: 'dummyValue' }, ], }, @@ -1325,18 +1312,7 @@ describe('Fetchr Server API Test', () => { principal: 'user.dummy1', assertions: [ { - role: 'dummy.project:role.gcp.fed.power.user', - resource: 'dummy.project:fed.power.user', - action: 'gcp.assume_role', - effect: 'ALLOW', - id: 1, - }, - { - role: 'dummy.project2:role.gcp.fed.admin.user', - resource: 'dummy.project2:fed.admin.user', - action: 'gcp.assume_role', - effect: 'ALLOW', - id: 2, + dummyProperty: 'dummyValue' }, ], }, diff --git a/ui/src/pages/gcp/login.js b/ui/src/pages/gcp/login.js deleted file mode 100644 index 4fdfd85cf09..00000000000 --- a/ui/src/pages/gcp/login.js +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright The Athenz Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import Button from '../../components/denali/Button'; -import API from '../../api'; -import Error from '../_error'; -import styled from '@emotion/styled'; -import createCache from '@emotion/cache'; -import RequestUtils from '../../components/utils/RequestUtils'; -import { selectIsLoading } from '../../redux/selectors/loading'; -import { selectUserResourceAccessList } from '../../redux/selectors/user'; -import { getUserResourceAccessList } from '../../redux/thunks/user'; -import { connect } from 'react-redux'; -import { CacheProvider } from '@emotion/react'; -import { ReduxPageLoader } from '../../components/denali/ReduxPageLoader'; -import { USER_DOMAIN } from '../../components/constants/constants'; - -const GcpHeader = styled.header` - border-bottom: 1px solid lightgray; - margin: 10px; - margin-bottom: 20px; -`; - -const ParentWrapperDiv = styled.div` - max-width: 800px; - margin: 0 auto; -`; - -const StyledP = styled.p` - font-size: 16px; -`; - -const RadioButtonsContainer = styled.div` - display: flex; - flex-direction: column; - margin-top: 10px; - margin-bottom: 10px; - margin-left: 20px; - font-weight: bold; -`; - -const ProjectTitleDiv = styled.div` - font-size: 18px; - display: inline; - border-bottom: 1px solid lightgray; - margin-bottom: 10px; - padding: 10px; - width: 100%; - display: block; -`; - -const RadioButton = styled.input` - vertical-align: middle; - width: 18px; - height: 18px; - margin: 10px; -`; - -const ProjectLabel = styled.label` - margin: 0; - font-weight: bold; - font-size: 16px; - color: #000; - vertical-align: middle; -`; - -const SubmitContainer = styled.div` - display: flex; -`; - -function getAssertionsFromResourceAccessList(resourceAccessList, userAuthority) { - let assertionsList = []; - if (resourceAccessList.resources) { - resourceAccessList.resources.forEach(function (resources) { - resources.assertions.forEach(function (assertion) { - if (userAuthority === 'all') { - assertionsList.push(assertion); - return; - } - if (assertion.role.toLowerCase().indexOf('admin') > -1) { - if (userAuthority === 'admin') { - assertionsList.push(assertion); - } - } else if (userAuthority === 'dev') { - assertionsList.push(assertion); - } - }); - }); - } - return assertionsList; -} - -function getProjectDomainName(role) { - let projectDomainNameAndRole = role.split(':role.'); - let projectDomainName = projectDomainNameAndRole[0]; - if (!projectDomainName) return ''; - return projectDomainName; -} - -function getRoleName(role) { - let projectDomainNameAndRole = role.split(':role.'); - if (projectDomainNameAndRole.length < 1) return ''; - let roleName = projectDomainNameAndRole[1]; - return roleName; -} - -function getProjectID(resource) { - let splitStrings = resource.split('/'); - if ( - splitStrings.length < 4 || - splitStrings[0] !== 'projects' || - splitStrings[2] !== 'roles' - ) - return ''; - let projectID = splitStrings[1]; - return projectID; -} - -export async function getServerSideProps(context) { - const api = API(context.req); - let reload = false; - let notFound = false; - let error = null; - const domains = await Promise.all([api.getForm()]).catch((err) => { - let response = RequestUtils.errorCheckHelper(err); - reload = response.reload; - error = response.error; - return [{}]; - }); - - let queryParams = context.query || {}; - let isAdmin = queryParams.isAdmin === 'true'; - let userAuthority = queryParams.userAuthority || 'dev'; - let startPath = queryParams.startPath || ''; - let projectDomainName = queryParams.projectDomainName || ''; - let validationError = queryParams.validationError || ''; - return { - props: { - reload, - notFound, - error, - validationError, - isAdmin, - startPath, - userAuthority, - projectDomainName, - _csrf: domains[0], - }, - }; -} - -class GCPLoginPage extends React.Component { - constructor(props) { - super(props); - this.api = API(); - this.cache = createCache({ - key: 'athenz', - nonce: this.props.nonce, - }); - this.state = { - errorMessage: '', - projectRoleMap: {}, - roleName: '', - }; - this.getAssertionList = this.getAssertionList.bind(this); - this.populateProjectRoleMap = this.populateProjectRoleMap.bind(this); - this.showError = this.showError.bind(this); - } - - componentDidMount() { - const { getResourceAccessList } = this.props; - Promise.all([ - getResourceAccessList({ - action: 'gcp.assume_role', - }), - ]).catch((err) => { - this.showError(RequestUtils.fetcherErrorCheckHelper(err)); - }); - } - - componentDidUpdate(prevProps) { - const { resourceAccessList } = this.props; - if (prevProps && prevProps.resourceAccessList !== resourceAccessList) { - let assertionList = this.getAssertionList(); - this.populateProjectRoleMap(assertionList); - } - } - - showError(errorMessage) { - this.setState((prevState) => ({ - ...prevState, - errorMessage: errorMessage, - })); - } - - getAssertionList() { - const { resourceAccessList, isAdmin, projectDomainName, userAuthority } = this.props; - let assertionsList = getAssertionsFromResourceAccessList( - resourceAccessList, - userAuthority - ); - // filter project list if projectDomainName is specified in the query - if (projectDomainName) { - assertionsList = assertionsList.filter( - (assertion) => assertion.role.indexOf(projectDomainName) > -1 - ); - } - return assertionsList; - } - - populateProjectRoleMap(assertionsList) { - let projectRoleMap = {}; - assertionsList.forEach((assertion) => { - let projectName = getProjectDomainName(assertion.role); - let projectRoleName = getRoleName(assertion.role); - let projectID = getProjectID(assertion.resource); - if (!projectName || !projectRoleName || !projectID) { - return; - } - let project = { - roleName: assertion.role, // value passed to gcp - projectRoleName, // For UI readability - projectName, // For UI readability - projectID, // For UI readability - }; - if (!projectRoleMap[projectName]) { - projectRoleMap[projectName] = [project]; - } else { - projectRoleMap[projectName].push(project); - } - }); - this.setState((prevState) => ({ - ...prevState, - projectRoleMap, - })); - } - - handleRadioButton(projectObject) { - this.setState((prevState) => ({ - ...prevState, - roleName: projectObject.roleName, - })); - } - - render() { - let errorMessage = this.props.error || this.state.errorMessage; - if (this.props.reload) { - window.location.reload(); - return
; - } - if (errorMessage) { - return ; - } - let displayProjects = []; - for (let pName in this.state.projectRoleMap) { - let projectRoleNames = []; - let projectID = this.state.projectRoleMap[pName][0].projectID; - this.state.projectRoleMap[pName].forEach((projectObject, index) => { - projectRoleNames.push( -
- - - this.handleRadioButton(projectObject) - } - required={true} - /> - {projectObject.projectRoleName} - -
- ); - }); - - displayProjects.push( -
- - Project: {projectID} ({pName}) - - - {projectRoleNames} - -
- ); - } - - let gcpLoginContainer = displayProjects.length ? ( -
- - - - -
- - - - Select a role: - {displayProjects} - - - -
-
-
- ) : ( -
- - - - -

- Error: There are no GCP project roles associated with - your account. -

-

- Check to make sure that your gcp.* roles - contain your user. Your account should be in the - configured federated roles that have access to the - project. If that does not work, please ask your Athenz - domain admin for assistance. -

-
-
- ); - return this.props.isLoading.length !== 0 ? ( - - ) : ( - - {gcpLoginContainer} - - ); - } -} - -const mapStateToProps = (state, props) => { - return { - ...props, - isLoading: selectIsLoading(state), - resourceAccessList: selectUserResourceAccessList(state), - }; -}; - -const mapDispatchToProps = (dispatch) => ({ - getResourceAccessList: (action) => - dispatch(getUserResourceAccessList(action)), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(GCPLoginPage);