Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • andretol/greg
1 result
Show changes
Commits on Source (11)
Showing
with 592 additions and 27 deletions
File added
This diff is collapsed.
File added
File added
File added
/* generated using https://google-webfonts-helper.herokuapp.com/fonts/roboto?subsets=latin */
/* roboto-regular - latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: url('./fonts/roboto-v29-latin-regular.eot'); /* IE9 Compat Modes */
src: local(''),
url('./fonts/roboto-v29-latin-regular.eot?#iefix')
format('embedded-opentype'),
/* IE6-IE8 */ url('./fonts/roboto-v29-latin-regular.woff2') format('woff2'),
/* Super Modern Browsers */ url('./fonts/roboto-v29-latin-regular.woff')
format('woff'),
/* Modern Browsers */ url('./fonts/roboto-v29-latin-regular.ttf')
format('truetype'),
/* Safari, Android, iOS */
url('./fonts/roboto-v29-latin-regular.svg#Roboto') format('svg'); /* Legacy iOS */
}
...@@ -13,6 +13,8 @@ import { UserProvider } from 'providers' ...@@ -13,6 +13,8 @@ import { UserProvider } from 'providers'
import reportWebVitals from './reportWebVitals' import reportWebVitals from './reportWebVitals'
import FeatureProvider from './providers/featureProvider' import FeatureProvider from './providers/featureProvider'
import './index.css'
function appRoot() { function appRoot() {
return ( return (
<React.StrictMode> <React.StrictMode>
......
...@@ -2,6 +2,8 @@ import { styled } from '@mui/material/styles' ...@@ -2,6 +2,8 @@ import { styled } from '@mui/material/styles'
import { Box, Link, useMediaQuery } from '@mui/material' import { Box, Link, useMediaQuery } from '@mui/material'
import LogoutIcon from '@mui/icons-material/Logout' import LogoutIcon from '@mui/icons-material/Logout'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Link as RouterLink } from 'react-router-dom'
import { useUserContext } from 'contexts' import { useUserContext } from 'contexts'
import LanguageSelector from 'components/languageselector' import LanguageSelector from 'components/languageselector'
import UserInfo from 'routes/components/userInfo' import UserInfo from 'routes/components/userInfo'
...@@ -56,7 +58,11 @@ const Header = () => { ...@@ -56,7 +58,11 @@ const Header = () => {
return ( return (
<StyledHeader> <StyledHeader>
<MainContainer> <MainContainer>
<LogoContainer>{getHeaderLogo()}</LogoContainer> <LogoContainer>
<Link component={RouterLink} to="/">
{getHeaderLogo()}
</Link>
</LogoContainer>
<Menu> <Menu>
<LanguageSelector noPadding /> <LanguageSelector noPadding />
{spaceAvailable && <UserInfo />} {spaceAvailable && <UserInfo />}
......
...@@ -233,7 +233,10 @@ const InvitedGuests = ({ persons }: GuestProps) => { ...@@ -233,7 +233,10 @@ const InvitedGuests = ({ persons }: GuestProps) => {
</Typography> </Typography>
</StyledAccordionSummary> </StyledAccordionSummary>
<AccordionDetails> <AccordionDetails>
<Typography variant="body1"> <Typography
variant="body1"
sx={{ marginTop: '1rem', marginBottom: '1rem' }}
>
{t('common:sentInvitationsDescription')} {t('common:sentInvitationsDescription')}
</Typography> </Typography>
<GuestTable guests={guests} emptyText={t('common:noInvitations')} /> <GuestTable guests={guests} emptyText={t('common:noInvitations')} />
...@@ -266,7 +269,10 @@ const ActiveGuests = ({ persons }: GuestProps) => { ...@@ -266,7 +269,10 @@ const ActiveGuests = ({ persons }: GuestProps) => {
</Typography> </Typography>
</StyledAccordionSummary> </StyledAccordionSummary>
<AccordionDetails> <AccordionDetails>
<Typography variant="body1"> <Typography
variant="body1"
sx={{ marginTop: '1rem', marginBottom: '1rem' }}
>
{t('common:activeGuestsDescription')} {t('common:activeGuestsDescription')}
</Typography> </Typography>
<GuestTable guests={guests} emptyText={t('common:noActiveGuests')} /> <GuestTable guests={guests} emptyText={t('common:noActiveGuests')} />
...@@ -305,7 +311,10 @@ const WaitingGuests = ({ persons }: GuestProps) => { ...@@ -305,7 +311,10 @@ const WaitingGuests = ({ persons }: GuestProps) => {
</Typography> </Typography>
</StyledAccordionSummary> </StyledAccordionSummary>
<AccordionDetails> <AccordionDetails>
<Typography variant="body1"> <Typography
variant="body1"
sx={{ marginTop: '1rem', marginBottom: '1rem' }}
>
<Trans i18nKey="common:waitingGuestsDescription"> <Trans i18nKey="common:waitingGuestsDescription">
Her godkjenner du gjester som har <b>registrert seg manuelt</b>. Her godkjenner du gjester som har <b>registrert seg manuelt</b>.
Gjester som har FEIDE-bruker trenger ikke godkjenning. Gjester som har FEIDE-bruker trenger ikke godkjenning.
......
...@@ -162,8 +162,10 @@ export default function GuestInfo({ ...@@ -162,8 +162,10 @@ export default function GuestInfo({
<SponsorInfoButtons to="/sponsor" name={`${guest.first} ${guest.last}`} /> <SponsorInfoButtons to="/sponsor" name={`${guest.first} ${guest.last}`} />
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<Box sx={{ marginBottom: '2rem' }}> <Box sx={{ marginBottom: '2rem' }}>
<Typography variant="h2">{t('guestInfo.contactInfo')}</Typography> <Typography sx={{ marginBottom: '1rem' }} variant="h2">
<Typography variant="body1"> {t('guestInfo.contactInfo')}
</Typography>
<Typography sx={{ marginBottom: '1rem' }} variant="body1">
{t('guestInfo.contactInfoBody')} {t('guestInfo.contactInfoBody')}
</Typography> </Typography>
...@@ -173,7 +175,6 @@ export default function GuestInfo({ ...@@ -173,7 +175,6 @@ export default function GuestInfo({
<TableRow> <TableRow>
<TableHeadCell align="left" colSpan={2}> <TableHeadCell align="left" colSpan={2}>
{t('guestInfo.contactInfoTableText')} {t('guestInfo.contactInfoTableText')}
</TableHeadCell> </TableHeadCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
...@@ -204,7 +205,7 @@ export default function GuestInfo({ ...@@ -204,7 +205,7 @@ export default function GuestInfo({
defaultValue={guest.email} defaultValue={guest.email}
inputRef={ref} inputRef={ref}
onChange={onChange} onChange={onChange}
{...inputProps} {...inputProps}
/> />
{/* If the guest has not completed the registration process, he should have an invitation he has not responded to */} {/* If the guest has not completed the registration process, he should have an invitation he has not responded to */}
...@@ -284,8 +285,12 @@ export default function GuestInfo({ ...@@ -284,8 +285,12 @@ export default function GuestInfo({
</Button> </Button>
</Box> </Box>
</form> </form>
<Typography variant="h2">{t('guestInfo.roleInfoHead')}</Typography> <Typography sx={{ marginBottom: '1rem' }} variant="h2">
<Typography variant="body1">{t('guestInfo.roleInfoBody')}</Typography> {t('guestInfo.roleInfoHead')}
</Typography>
<Typography sx={{ marginBottom: '1rem' }} variant="body1">
{t('guestInfo.roleInfoBody')}
</Typography>
<TableContainer> <TableContainer>
<Table sx={{ minWidth: 650 }} aria-label="simple table"> <Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead> <TableHead>
......
...@@ -84,7 +84,7 @@ const TableCell = styled(TableCellMui)({ ...@@ -84,7 +84,7 @@ const TableCell = styled(TableCellMui)({
}) })
const TableContainer = styled(TableContainerMui)({ const TableContainer = styled(TableContainerMui)({
marginBottom: '0.8rem', marginBottom: '1rem',
borderRadius: '1%', borderRadius: '1%',
borderStyle: 'solid', borderStyle: 'solid',
borderColor: 'black', borderColor: 'black',
...@@ -194,8 +194,12 @@ export default function GuestRoleInfo({ ...@@ -194,8 +194,12 @@ export default function GuestRoleInfo({
to={`/sponsor/guest/${pid}`} to={`/sponsor/guest/${pid}`}
name={`${guest.first} ${guest.last}`} name={`${guest.first} ${guest.last}`}
/> />
<Typography variant="h2">{t('sponsor.roleInfoHead')}</Typography> <Typography sx={{ marginBottom: '1rem' }} variant="h2">
<Typography variant="body1">{t('sponsor.roleInfoText')}</Typography> {t('sponsor.roleInfoHead')}
</Typography>
<Typography sx={{ marginBottom: '1rem' }} variant="body1">
{t('sponsor.roleInfoText')}
</Typography>
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<TableContainer> <TableContainer>
<Table sx={{ minWidth: 650 }} aria-label="simple table"> <Table sx={{ minWidth: 650 }} aria-label="simple table">
......
...@@ -182,8 +182,12 @@ function NewGuestRole({ guest, reloadGuestInfo }: NewGuestRoleProps) { ...@@ -182,8 +182,12 @@ function NewGuestRole({ guest, reloadGuestInfo }: NewGuestRoleProps) {
to={`/sponsor/guest/${pid}`} to={`/sponsor/guest/${pid}`}
name={`${guest.first} ${guest.last}`} name={`${guest.first} ${guest.last}`}
/> />
<Typography variant="h2">{t('guest.headerText')}</Typography> <Typography sx={{ marginBottom: '1rem' }} variant="h2">
<Typography variant="body1">{t('guest.bodyText')}</Typography> {t('guest.headerText')}
</Typography>
<Typography sx={{ marginBottom: '1rem' }} variant="body1">
{t('guest.bodyText')}
</Typography>
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<Stack spacing={2}> <Stack spacing={2}>
<FormControl> <FormControl>
......
...@@ -57,14 +57,19 @@ function FrontPage() { ...@@ -57,14 +57,19 @@ function FrontPage() {
return ( return (
<Page> <Page>
<SponsorGuestButtons registerNewGuestActive /> <SponsorGuestButtons registerNewGuestActive />
<Typography variant="h2">{t('register.registerHeading')}</Typography> <Typography variant="h2" sx={{ marginBottom: '1rem' }}>
<Typography variant="body1">{t('register.registerText')}</Typography> {t('register.registerHeading')}
</Typography>
<Typography variant="body1" sx={{ marginBottom: '1rem' }}>
{t('register.registerText')}
</Typography>
<Box <Box
sx={{ sx={{
borderStyle: () => (guests.length > 0 ? 'solid' : 'none'), borderStyle: () => (guests.length > 0 ? 'solid' : 'none'),
borderRadius: '0.3125rem', borderRadius: '0.3125rem',
borderColor: 'secondary.main', borderColor: 'secondary.main',
padding: '0.4375 0.75rem', padding: '0.4375 0.75rem',
marginBottom: '2rem',
}} }}
> >
<TextField <TextField
...@@ -105,7 +110,6 @@ function FrontPage() { ...@@ -105,7 +110,6 @@ function FrontPage() {
}) })
)} )}
</Box> </Box>
<br />
<Button <Button
variant="contained" variant="contained"
color="secondary" color="secondary"
......
...@@ -114,6 +114,24 @@ def test_persons_verified_filter_exclude( ...@@ -114,6 +114,24 @@ def test_persons_verified_filter_exclude(
assert results[0]["last_name"] == "Bar" assert results[0]["last_name"] == "Bar"
@pytest.mark.django_db
def test_person_create(client):
url = reverse("v1:person-list")
response = client.post(url, {"first_name": "foo木👍أ", "last_name": "غbar水"})
results = response.json()
assert results == {
"id": 1,
"first_name": "foo木👍أ",
"last_name": "غbar水",
"gender": None,
"date_of_birth": None,
"registration_completed_date": None,
"identities": [],
"roles": [],
"consents": [],
}
@pytest.mark.django_db @pytest.mark.django_db
def test_add_role( def test_add_role(
client, person, role_type_visiting_professor, sponsor_guy, unit_human_resources client, person, role_type_visiting_professor, sponsor_guy, unit_human_resources
......
...@@ -5,6 +5,9 @@ import pytest ...@@ -5,6 +5,9 @@ import pytest
from django.utils import timezone from django.utils import timezone
from greg.models import ( from greg.models import (
Consent,
ConsentChoice,
ConsentType,
OrganizationalUnit, OrganizationalUnit,
Person, Person,
Identity, Identity,
...@@ -190,3 +193,50 @@ def test_person_str(person: Person): ...@@ -190,3 +193,50 @@ def test_person_str(person: Person):
@pytest.mark.django_db @pytest.mark.django_db
def test_person_repr(person: Person): def test_person_repr(person: Person):
assert repr(person) == "Person(id=1, first_name='Test', last_name='Tester')" assert repr(person) == "Person(id=1, first_name='Test', last_name='Tester')"
@pytest.mark.django_db
def test_person_has_mandatory_consents(person):
"""
No consents exists.
Should return True
"""
assert person.has_mandatory_consents is True
@pytest.mark.django_db
def test_person_has_mandatory_consents_only_optional(person):
"""
An optional consent exists, and the person has not given consent.
Should return True
"""
ConsentType.objects.create(user_allowed_to_change=True)
assert person.has_mandatory_consents is True
@pytest.mark.django_db
def test_person_has_mandatory_consents_mandatory_exists(person):
"""
A mandatory consent exists, and the person has not given it.
Should return False
"""
ConsentType.objects.create(user_allowed_to_change=True, mandatory=True)
assert person.has_mandatory_consents is False
@pytest.mark.django_db
def test_person_has_mandatory_consents_mandatory_given(person):
"""
A mandatory consent exists, and the person has given it.
Should return True
"""
ct = ConsentType.objects.create(user_allowed_to_change=True, mandatory=True)
cc = ConsentChoice.objects.create(consent_type=ct)
Consent.objects.create(
person=person, type=ct, choice=cc, consent_given_at=timezone.now()
)
assert person.has_mandatory_consents is True
...@@ -31,8 +31,7 @@ class RoleInfoViewSet(ModelViewSet): ...@@ -31,8 +31,7 @@ class RoleInfoViewSet(ModelViewSet):
context={"sponsor": sponsor}, context={"sponsor": sponsor},
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
instance = serializer.update(role, serializer.validated_data) serializer.update(role, serializer.validated_data)
instance.save()
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)
def create(self, request): def create(self, request):
......
...@@ -243,7 +243,7 @@ class GregOIDCBackend(ValidatingOIDCBackend): ...@@ -243,7 +243,7 @@ class GregOIDCBackend(ValidatingOIDCBackend):
extended_userinfo = self.get_extended_userinfo(access_token) extended_userinfo = self.get_extended_userinfo(access_token)
if "norEduPersonNIN" in extended_userinfo: if "norEduPersonNIN" in extended_userinfo:
userinfo["userid_nin"] = extended_userinfo["norEduPersonNIN"] userinfo["userid_nin"] = extended_userinfo["norEduPersonNIN"]
self._get_or_create_greg_user_profile(userinfo, user)
return user return user
def _get_sponsor_from_userinfo(self, userinfo: dict) -> Optional[Sponsor]: def _get_sponsor_from_userinfo(self, userinfo: dict) -> Optional[Sponsor]:
......
...@@ -21,8 +21,8 @@ def test_invite_guest(client, user_sponsor, unit_foo, role_type_foo, mocker): ...@@ -21,8 +21,8 @@ def test_invite_guest(client, user_sponsor, unit_foo, role_type_foo, mocker):
role_end_date = datetime.datetime.today() + datetime.timedelta(days=10) role_end_date = datetime.datetime.today() + datetime.timedelta(days=10)
data = { data = {
"first_name": "Foo", "first_name": "foo木👍أ",
"last_name": "Bar", "last_name": "غbar",
"email": "test@example.com", "email": "test@example.com",
"role": { "role": {
"start_date": (role_start_date).strftime("%Y-%m-%d"), "start_date": (role_start_date).strftime("%Y-%m-%d"),
...@@ -46,8 +46,8 @@ def test_invite_guest(client, user_sponsor, unit_foo, role_type_foo, mocker): ...@@ -46,8 +46,8 @@ def test_invite_guest(client, user_sponsor, unit_foo, role_type_foo, mocker):
assert Person.objects.count() == 1 assert Person.objects.count() == 1
person = Person.objects.first() person = Person.objects.first()
assert person.first_name == "Foo" assert person.first_name == "foo木👍أ"
assert person.last_name == "Bar" assert person.last_name == "غbar"
assert Identity.objects.filter( assert Identity.objects.filter(
person=person, person=person,
......
...@@ -24,7 +24,7 @@ def test_userinfo_invited_get(client, invitation_link): ...@@ -24,7 +24,7 @@ def test_userinfo_invited_get(client, invitation_link):
"sponsor_id": None, "sponsor_id": None,
"person_id": 1, "person_id": 1,
"first_name": "Foo", "first_name": "Foo",
"last_name": "Bar", "last_name": "Baمr",
"email": "foo@example.org", "email": "foo@example.org",
"mobile_phone": None, "mobile_phone": None,
"fnr": None, "fnr": None,
...@@ -85,7 +85,7 @@ def test_userinfo_guest_get(client, log_in, user_person): ...@@ -85,7 +85,7 @@ def test_userinfo_guest_get(client, log_in, user_person):
"person_id": 1, "person_id": 1,
"roles": [], "roles": [],
"consents": [], "consents": [],
"first_name": "Foo", "first_name": "Fooم",
"last_name": "Bar", "last_name": "Bar",
"email": "foo@bar.com", "email": "foo@bar.com",
"mobile_phone": None, "mobile_phone": None,
......
...@@ -318,3 +318,141 @@ def test_invited_existing_user(invited_person, user_person): ...@@ -318,3 +318,141 @@ def test_invited_existing_user(invited_person, user_person):
user_profile = GregUserProfile.objects.get(user=user) user_profile = GregUserProfile.objects.get(user=user)
assert user_profile.person == old_person assert user_profile.person == old_person
@pytest.mark.django_db
def test_invited_feide_nin_from_extended(requests_mock, invited_person):
"""
Invitation login, user with existing person and no sponsor object,
nin only present in extended userinfo.
A GregUserProfile should be created, and the person should get feide-id and nin set
"""
person, invitation_link = invited_person
email = "foo@example.com"
feide_id = "bar@example.com"
nin = "12345678901"
access_token = "foo"
id_token = "foo"
payload = "foo"
requests_mock.get(
"https://auth.dataporten.no/openid/userinfo",
json={
"sub": "subsub",
"dataporten-userid_sec": [f"feide:{feide_id}"],
"name": f"{person.first_name} {person.last_name}",
"email": f"{email}",
},
)
requests_mock.get(
"https://api.dataporten.no/userinfo/v1/userinfo", json={"norEduPersonNIN": nin}
)
auth_request = RequestFactory().get("/foo", {"code": "foo", "state": "bar"})
auth_request.session = {"invite_id": invitation_link.uuid}
backend = GregOIDCBackend()
backend.request = auth_request
# No profile beforehand
assert len(GregUserProfile.objects.filter(person_id=person.id)) == 0
user = backend.get_or_create_user(access_token, id_token, payload)
# User profile exists and all things set correctly after
user_profile = GregUserProfile.objects.get(user=user)
assert user_profile.person == person
assert user_profile.person.fnr and user_profile.person.fnr.value == nin
assert (
user_profile.person.feide_id and user_profile.person.feide_id.value == feide_id
)
assert user_profile.sponsor is None
@pytest.mark.django_db
def test_invited_feide_nin_from_regular(requests_mock, invited_person):
"""
Invitation login, user with existing person and no sponsor object,
nin present in regular userinfo.
A GregUserProfile should be created, and the person should get feide-id and nin set
"""
person, invitation_link = invited_person
email = "foo@example.com"
feide_id = "bar@example.com"
nin = "12345678901"
access_token = "foo"
id_token = "foo"
payload = "foo"
requests_mock.get(
"https://auth.dataporten.no/openid/userinfo",
json={
"sub": "subsub",
"dataporten-userid_sec": [f"feide:{feide_id}", f"nin:{nin}"],
"name": f"{person.first_name} {person.last_name}",
"email": f"{email}",
},
)
requests_mock.get("https://api.dataporten.no/userinfo/v1/userinfo", json={})
auth_request = RequestFactory().get("/foo", {"code": "foo", "state": "bar"})
auth_request.session = {"invite_id": invitation_link.uuid}
backend = GregOIDCBackend()
backend.request = auth_request
# No profile beforehand
assert len(GregUserProfile.objects.filter(person_id=person.id)) == 0
user = backend.get_or_create_user(access_token, id_token, payload)
# User profile exists and all things set correctly after
user_profile = GregUserProfile.objects.get(user=user)
assert user_profile.person == person
assert user_profile.person.fnr and user_profile.person.fnr.value == nin
assert (
user_profile.person.feide_id and user_profile.person.feide_id.value == feide_id
)
assert user_profile.sponsor is None
@pytest.mark.django_db
def test_invited_feide_no_nin(requests_mock, invited_person):
"""
Invitation login, user with existing person and no sponsor object,
nin not present
A GregUserProfile should be created, and the person should get feide-id set
"""
person, invitation_link = invited_person
email = "foo@example.com"
feide_id = "bar@example.com"
access_token = "foo"
id_token = "foo"
payload = "foo"
requests_mock.get(
"https://auth.dataporten.no/openid/userinfo",
json={
"sub": "subsub",
"dataporten-userid_sec": [f"feide:{feide_id}"],
"name": f"{person.first_name} {person.last_name}",
"email": f"{email}",
},
)
requests_mock.get("https://api.dataporten.no/userinfo/v1/userinfo", json={})
auth_request = RequestFactory().get("/foo", {"code": "foo", "state": "bar"})
auth_request.session = {"invite_id": invitation_link.uuid}
backend = GregOIDCBackend()
backend.request = auth_request
# No profile beforehand
assert len(GregUserProfile.objects.filter(person_id=person.id)) == 0
user = backend.get_or_create_user(access_token, id_token, payload)
# User profile exists and all things set correctly after
user_profile = GregUserProfile.objects.get(user=user)
assert user_profile.person == person
assert user_profile.person.fnr is None
assert (
user_profile.person.feide_id and user_profile.person.feide_id.value == feide_id
)
assert user_profile.sponsor is None