mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #27026 from overleaf/jel-domain-capture-check
[web] Check if domain is captured by group when using domain API GitOrigin-RevId: 6e14df0a1701c33fc80f21f01bb3fbb446d7f074
This commit is contained in:
@@ -20,6 +20,8 @@ import { useRecaptcha } from '../../../../shared/hooks/use-recaptcha'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import { ConfirmEmailForm } from '@/features/settings/components/emails/confirm-email-form'
|
||||
import RecaptchaConditions from '@/shared/components/recaptcha-conditions'
|
||||
import SsoLinkingInfoGroup from './add-email/sso-linking-info-group'
|
||||
import Notification from '@/shared/components/notification'
|
||||
|
||||
function AddEmail() {
|
||||
const { t } = useTranslation()
|
||||
@@ -240,9 +242,9 @@ function AddEmail() {
|
||||
<OLCol lg={12}>
|
||||
<Cell>
|
||||
<div className="affiliations-table-cell-tabbed">
|
||||
<SsoLinkingInfo
|
||||
<AddEmailViaSSO
|
||||
email={newEmail}
|
||||
domainInfo={newEmailMatchedDomain as DomainInfo}
|
||||
domainInfo={newEmailMatchedDomain}
|
||||
/>
|
||||
</div>
|
||||
</Cell>
|
||||
@@ -254,4 +256,39 @@ function AddEmail() {
|
||||
)
|
||||
}
|
||||
|
||||
function AddEmailViaSSO({
|
||||
email,
|
||||
domainInfo,
|
||||
}: {
|
||||
email: string
|
||||
domainInfo: DomainInfo
|
||||
}) {
|
||||
if (domainInfo.university.ssoEnabled) {
|
||||
// SSO for Commons institution
|
||||
return <SsoLinkingInfo email={email} domainInfo={domainInfo} />
|
||||
} else if (
|
||||
domainInfo.group?.domainCaptureEnabled &&
|
||||
domainInfo.group?.managedUsersEnabled
|
||||
) {
|
||||
return (
|
||||
<Notification
|
||||
type="error"
|
||||
ariaLive="polite"
|
||||
content={
|
||||
<>
|
||||
Your company email address has been registered under a verified
|
||||
domain, and cannot be added as a secondary email. Please create a
|
||||
new <strong>Overleaf</strong> account linked to this email address.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else if (
|
||||
domainInfo.group?.domainCaptureEnabled &&
|
||||
domainInfo.group?.ssoConfig?.enabled
|
||||
) {
|
||||
return <SsoLinkingInfoGroup domainInfo={domainInfo} />
|
||||
}
|
||||
}
|
||||
|
||||
export default AddEmail
|
||||
|
||||
@@ -34,6 +34,15 @@ export type DomainInfo = {
|
||||
ssoBeta?: boolean
|
||||
departments?: string[]
|
||||
}
|
||||
group: {
|
||||
teamName?: string
|
||||
managedUsersEnabled?: boolean
|
||||
domainCaptureEnabled?: boolean
|
||||
ssoConfig?: {
|
||||
useUkamfSettings?: boolean
|
||||
enabled: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let domainCache = new Map<string, DomainInfo>()
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { DomainInfo } from './input'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
type SSOLinkingInfoProps = {
|
||||
domainInfo: DomainInfo
|
||||
}
|
||||
|
||||
function SsoLinkingInfoGroup({ domainInfo }: SSOLinkingInfoProps) {
|
||||
if (!domainInfo.group.ssoConfig) {
|
||||
return
|
||||
}
|
||||
|
||||
const institutionName =
|
||||
domainInfo.group.teamName || domainInfo.university.name
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="to_add_email_accounts_need_to_be_linked_2"
|
||||
components={[<strong />]} // eslint-disable-line react/jsx-key
|
||||
values={{ institutionName }}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p>This feature is currently unavailable.</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SsoLinkingInfoGroup
|
||||
@@ -12,6 +12,11 @@ export const ssoAvailableForDomain = (
|
||||
if (domain.university.ssoEnabled) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (domain.group?.ssoConfig?.enabled) {
|
||||
return true
|
||||
}
|
||||
|
||||
return Boolean(hasSamlBeta && domain.university.ssoBeta)
|
||||
}
|
||||
|
||||
|
||||
@@ -362,6 +362,23 @@ class MockV1Api extends AbstractMockApi {
|
||||
},
|
||||
},
|
||||
])
|
||||
} else if (req.query.hostname === 'sharelatex.com') {
|
||||
res.json([
|
||||
{
|
||||
id: 44,
|
||||
hostname: 'sharelatex.com',
|
||||
department: 'test dept',
|
||||
confirmed: true,
|
||||
university: {
|
||||
id: 5000,
|
||||
name: 'Institution sharelatex',
|
||||
departments: [],
|
||||
ssoBeta: false,
|
||||
ssoEnabled: false,
|
||||
commons: false,
|
||||
},
|
||||
},
|
||||
])
|
||||
} else {
|
||||
res.json([])
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
screen,
|
||||
fireEvent,
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import EmailsSection from '../../../../../../frontend/js/features/settings/components/emails-section'
|
||||
@@ -12,6 +13,7 @@ import { UserEmailData } from '../../../../../../types/user-email'
|
||||
import { Affiliation } from '../../../../../../types/affiliation'
|
||||
import withMarkup from '../../../../helpers/with-markup'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { clearDomainCache } from '../../../../../../frontend/js/features/settings/components/emails/add-email/input'
|
||||
|
||||
const userEmailData: UserEmailData & { affiliation: Affiliation } = {
|
||||
affiliation: {
|
||||
@@ -87,6 +89,7 @@ describe('<EmailsSection />', function () {
|
||||
|
||||
afterEach(function () {
|
||||
resetFetchMock()
|
||||
clearDomainCache()
|
||||
})
|
||||
|
||||
it('renders "add another email" button', async function () {
|
||||
@@ -709,4 +712,141 @@ describe('<EmailsSection />', function () {
|
||||
exact: false,
|
||||
})
|
||||
})
|
||||
|
||||
describe('when domain is captured by a group', function () {
|
||||
describe('and managed users is not enabled', function () {
|
||||
beforeEach(async function () {
|
||||
await fetchMock.callHistory.flush(true)
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
const institution = {
|
||||
university: {
|
||||
id: 1234,
|
||||
ssoEnabled: false,
|
||||
name: 'Auto Complete University',
|
||||
},
|
||||
hostname: 'autocomplete.edu',
|
||||
confirmed: true,
|
||||
group: {
|
||||
domainCaptureEnabled: true,
|
||||
ssoConfig: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetchMock.get('express:/institutions/domains', [institution])
|
||||
})
|
||||
|
||||
it('can add email address via SSO', async function () {
|
||||
// note: this UI is a WIP
|
||||
fetchMock.get('/user/emails?ensureAffiliation=true', [])
|
||||
render(<EmailsSection />)
|
||||
|
||||
const button = await screen.findByRole<HTMLButtonElement>('button', {
|
||||
name: /add another email/i,
|
||||
})
|
||||
|
||||
await userEvent.click(button)
|
||||
|
||||
const input = screen.getByLabelText(/email/i, { selector: 'input' })
|
||||
fireEvent.change(input, {
|
||||
target: { value: 'user@autocomplete.edu' },
|
||||
})
|
||||
await screen.findByText('This feature is currently unavailable.')
|
||||
})
|
||||
})
|
||||
|
||||
describe('and managed users is enabled', function () {
|
||||
beforeEach(async function () {
|
||||
await fetchMock.callHistory.flush(true)
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
const institution = {
|
||||
university: {
|
||||
id: 1234,
|
||||
ssoEnabled: false,
|
||||
name: 'Auto Complete University',
|
||||
},
|
||||
hostname: 'autocomplete.edu',
|
||||
confirmed: true,
|
||||
group: {
|
||||
domainCaptureEnabled: true,
|
||||
managedUsersEnabled: true,
|
||||
ssoConfig: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetchMock.get('express:/institutions/domains', [institution])
|
||||
})
|
||||
|
||||
it('renders error', async function () {
|
||||
// note: this UI is a WIP
|
||||
fetchMock.get('/user/emails?ensureAffiliation=true', [])
|
||||
render(<EmailsSection />)
|
||||
|
||||
const button = await screen.findByRole<HTMLButtonElement>('button', {
|
||||
name: /add another email/i,
|
||||
})
|
||||
|
||||
await userEvent.click(button)
|
||||
|
||||
const input = screen.getByLabelText(/email/i, { selector: 'input' })
|
||||
fireEvent.change(input, {
|
||||
target: { value: 'user@autocomplete.edu' },
|
||||
})
|
||||
|
||||
const notification = await screen.findByRole('alert')
|
||||
within(notification).getByText(
|
||||
'Your company email address has been registered under a verified domain, and cannot be added as a secondary email.',
|
||||
{ exact: false }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('if Commons SSO then enabled, that takes priority over group UI', function () {
|
||||
// we shouldn't have SSO config in v1 and in v2 but adding test to ensure Commons takes priority
|
||||
beforeEach(async function () {
|
||||
await fetchMock.callHistory.flush(true)
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
const institution = {
|
||||
university: {
|
||||
id: 1234,
|
||||
ssoEnabled: true,
|
||||
name: 'Auto Complete University',
|
||||
},
|
||||
hostname: 'autocomplete.edu',
|
||||
confirmed: true,
|
||||
group: {
|
||||
domainCaptureEnabled: true,
|
||||
ssoConfig: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetchMock.get('express:/institutions/domains', [institution])
|
||||
})
|
||||
|
||||
it('renders Commons UI', async function () {
|
||||
fetchMock.get('/user/emails?ensureAffiliation=true', [])
|
||||
render(<EmailsSection />)
|
||||
|
||||
const button = await screen.findByRole<HTMLButtonElement>('button', {
|
||||
name: /add another email/i,
|
||||
})
|
||||
|
||||
await userEvent.click(button)
|
||||
|
||||
const input = screen.getByLabelText(/email/i, { selector: 'input' })
|
||||
fireEvent.change(input, {
|
||||
target: { value: 'user@autocomplete.edu' },
|
||||
})
|
||||
|
||||
await screen.findByRole('button', {
|
||||
name: 'Link accounts and add email',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user