@eeacms/volto-eea-website-policy 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,17 +4,35 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
- ### [2.0.0](https://github.com/eea/volto-eea-website-policy/compare/1.14.1-beta.1...2.0.0) - 22 April 2024
7
+ ### [2.1.0](https://github.com/eea/volto-eea-website-policy/compare/2.0.0...2.1.0) - 9 May 2024
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: integrated EEA Azure AD authentication - refs #258877 [Alexandru Ghica - [`7ff72c1`](https://github.com/eea/volto-eea-website-policy/commit/7ff72c1567524ce73ec72e6a30e91d6d29342510)]
12
+
13
+ #### :hammer_and_wrench: Others
14
+
15
+ - Release 2.1.0 [alin - [`da22af4`](https://github.com/eea/volto-eea-website-policy/commit/da22af436092236d7ef6e8568e3eca4813d517b5)]
16
+ ## [2.0.0](https://github.com/eea/volto-eea-website-policy/compare/1.15.0...2.0.0) - 22 April 2024
8
17
 
9
18
  #### :rocket: New Features
10
19
 
11
20
  - feat: Release 2.0.0 - Volto 17 support [alin - [`d6cf5c8`](https://github.com/eea/volto-eea-website-policy/commit/d6cf5c8f41950a94cd68e7a36a45b5be8cc6195b)]
12
- - feat: Default view layout restriction to Manager - refs #268249 [ichim-david - [`1840c1b`](https://github.com/eea/volto-eea-website-policy/commit/1840c1bd9aae6ce18a23800ce618f8bbe47525ab)]
13
21
 
14
22
  #### :nail_care: Enhancements
15
23
 
16
24
  - refactor: Move customizations to volto-eea-website-theme [alin - [`9641437`](https://github.com/eea/volto-eea-website-policy/commit/9641437c7f10ba6c7ec1f896b180b49d2718a7d0)]
17
25
 
26
+ ### [1.15.0](https://github.com/eea/volto-eea-website-policy/compare/1.14.1-beta.1...1.15.0) - 9 May 2024
27
+
28
+ #### :rocket: New Features
29
+
30
+ - feat: integrated EEA Azure AD authentication - refs #258877 [Alexandru Ghica - [`1897659`](https://github.com/eea/volto-eea-website-policy/commit/18976594a9c7b4f2640dc79ab6420f509e7de668)]
31
+ - feat: Default view layout restriction to Manager - refs #268249 [ichim-david - [`1840c1b`](https://github.com/eea/volto-eea-website-policy/commit/1840c1bd9aae6ce18a23800ce618f8bbe47525ab)]
32
+
33
+ #### :hammer_and_wrench: Others
34
+
35
+ - Release 1.15.0 [alin - [`f0d61d8`](https://github.com/eea/volto-eea-website-policy/commit/f0d61d8bd99687f0460acd0a1ad6e200206b5b6a)]
18
36
  ### [1.14.1-beta.1](https://github.com/eea/volto-eea-website-policy/compare/1.14.1-beta.0...1.14.1-beta.1) - 19 April 2024
19
37
 
20
38
  #### :hammer_and_wrench: Others
@@ -1,4 +1,4 @@
1
- require('dotenv').config({ path: __dirname + '/.env' })
1
+ require('dotenv').config({ path: __dirname + '/.env' });
2
2
 
3
3
  module.exports = {
4
4
  testMatch: ['**/src/addons/**/?(*.)+(spec|test).[jt]s?(x)'],
@@ -8,6 +8,8 @@ module.exports = {
8
8
  ],
9
9
  moduleNameMapper: {
10
10
  '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
11
+ '@plone-collective/volto-authomatic/(.*)$':
12
+ '<rootDir>/node_modules/@plone-collective/volto-authomatic/src/$1',
11
13
  '@plone/volto/cypress': '<rootDir>/node_modules/@plone/volto/cypress',
12
14
  '@plone/volto/babel': '<rootDir>/node_modules/@plone/volto/babel',
13
15
  '@plone/volto/(.*)$': '<rootDir>/node_modules/@plone/volto/src/$1',
@@ -26,7 +28,7 @@ module.exports = {
26
28
  '<rootDir>/node_modules/@plone/volto/jest-addons-loader.js',
27
29
  },
28
30
  transformIgnorePatterns: [
29
- '/node_modules/(?!(@plone|@root|@package|@eeacms)/).*/',
31
+ '/node_modules/(?!(@plone|@root|@package|@eeacms|@plone-collective)/).*/',
30
32
  ],
31
33
  transform: {
32
34
  '^.+\\.js(x)?$': 'babel-jest',
@@ -48,4 +50,4 @@ module.exports = {
48
50
  '<rootDir>/node_modules/@eeacms/volto-eea-website-policy/jest.setup.js',
49
51
  ],
50
52
  }),
51
- }
53
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-website-policy",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "@eeacms/volto-eea-website-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -17,15 +17,21 @@
17
17
  "url": "git@github.com:eea/volto-eea-website-policy.git"
18
18
  },
19
19
  "addons": [
20
- "@eeacms/volto-widget-dataprovenance"
20
+ "@eeacms/volto-widget-dataprovenance",
21
+ "@plone-collective/volto-authomatic"
21
22
  ],
22
23
  "dependencies": {
23
- "@eeacms/volto-widget-dataprovenance": "*"
24
+ "@eeacms/volto-widget-dataprovenance": "*",
25
+ "@plone-collective/volto-authomatic": "2.0.1"
26
+ },
27
+ "resolutions": {
28
+ "@plone-collective/volto-authomatic": "2.0.1"
24
29
  },
25
30
  "devDependencies": {
26
31
  "@cypress/code-coverage": "^3.10.0",
27
32
  "@plone/scripts": "*",
28
33
  "babel-plugin-transform-class-properties": "^6.24.1",
34
+ "cypress": "13.1.0",
29
35
  "dotenv": "^16.3.2",
30
36
  "husky": "^8.0.3",
31
37
  "lint-staged": "^14.0.1",
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Login container.
3
+ * @module components/Login/Login
4
+ */
5
+ import React, { useEffect, useState } from 'react';
6
+ import {
7
+ authomaticRedirect,
8
+ listAuthOptions,
9
+ oidcRedirect,
10
+ } from '@plone-collective/volto-authomatic/actions';
11
+ import { injectIntl } from 'react-intl';
12
+ import { useSelector, useDispatch } from 'react-redux';
13
+ import { useLocation } from 'react-router-dom';
14
+ import LoginForm from '@plone-collective/volto-authomatic/components/Login/LoginForm';
15
+ import qs from 'query-string';
16
+ import { useCookies } from 'react-cookie';
17
+
18
+ /**
19
+ * Get retur url function.
20
+ * @function getReturnUrl
21
+ * @param {Object} location Location object.
22
+ * @returns {string} Return url.
23
+ */
24
+ function getReturnUrl(location) {
25
+ return `${
26
+ qs.parse(location.search).return_url ||
27
+ (location.pathname === '/azure_login'
28
+ ? '/'
29
+ : location.pathname.replace('/azure_login', ''))
30
+ }`;
31
+ }
32
+
33
+ /**
34
+ * Login function.
35
+ * @function Login
36
+ * @returns {JSX.Element} Markup of the Login page.
37
+ */
38
+ function Login({ intl }) {
39
+ const dispatch = useDispatch();
40
+ const [startedOAuth, setStartedOAuth] = useState(false);
41
+ const [startedOIDC, setStartedOIDC] = useState(false);
42
+ const loading = useSelector((state) => state.authOptions.loading);
43
+ const options = useSelector((state) => state.authOptions.options);
44
+ const loginOAuthValues = useSelector((state) => state.authomaticRedirect);
45
+ const loginOIDCValues = useSelector((state) => state.oidcRedirect);
46
+ const location = useLocation();
47
+ const [, setCookie] = useCookies();
48
+
49
+ useEffect(() => {
50
+ dispatch(listAuthOptions());
51
+ }, [dispatch]);
52
+
53
+ useEffect(() => {
54
+ const next_url = loginOAuthValues.next_url;
55
+ const session = loginOAuthValues.session;
56
+ if (next_url && session && startedOAuth) {
57
+ setStartedOAuth(false);
58
+ // Give time to save state to localstorage
59
+ setTimeout(function () {
60
+ window.location.href = next_url;
61
+ }, 500);
62
+ }
63
+ }, [startedOAuth, loginOAuthValues]);
64
+
65
+ const onSelectProvider = (provider) => {
66
+ setStartedOAuth(true);
67
+ setCookie('return_url', getReturnUrl(location), { path: '/' });
68
+ dispatch(authomaticRedirect(provider.id));
69
+ };
70
+
71
+ useEffect(() => {
72
+ const next_url = loginOIDCValues.next_url;
73
+ if (next_url && startedOIDC) {
74
+ setStartedOIDC(false);
75
+ // Give time to save state to localstorage
76
+ setTimeout(function () {
77
+ window.location.href = next_url;
78
+ }, 500);
79
+ }
80
+ }, [startedOIDC, loginOIDCValues]);
81
+
82
+ useEffect(() => {
83
+ if (
84
+ options !== undefined &&
85
+ options.length === 1 &&
86
+ options[0].id === 'oidc'
87
+ ) {
88
+ setStartedOIDC(true);
89
+ dispatch(oidcRedirect('oidc'));
90
+ }
91
+ }, [options, dispatch]);
92
+
93
+ return (
94
+ <LoginForm
95
+ loading={loading}
96
+ providers={options}
97
+ action={'login'}
98
+ onSelectProvider={onSelectProvider}
99
+ />
100
+ );
101
+ }
102
+
103
+ export default injectIntl(Login);
@@ -0,0 +1,179 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { Provider } from 'react-intl-redux';
3
+ import configureStore from 'redux-mock-store';
4
+ import { useLocation } from 'react-router-dom';
5
+ import { listAuthOptions } from '@plone-collective/volto-authomatic/actions';
6
+ import '@testing-library/jest-dom/extend-expect';
7
+
8
+ import Login from './Login';
9
+
10
+ const mockStore = configureStore();
11
+
12
+ jest.mock('react-router-dom', () => ({
13
+ ...jest.requireActual('react-router-dom'),
14
+ useLocation: jest.fn(),
15
+ }));
16
+
17
+ jest.useFakeTimers();
18
+
19
+ describe('Login component', () => {
20
+ beforeEach(() => {
21
+ useLocation.mockReturnValue({ pathname: '/azure_login' });
22
+ });
23
+
24
+ it('renders correctly with loading state', () => {
25
+ const store = mockStore({
26
+ authOptions: { loading: true, options: [] },
27
+ authomaticRedirect: {
28
+ next_url: '/dashboard',
29
+ session: true,
30
+ },
31
+ oidcRedirect: {
32
+ next_url: '/dashboard',
33
+ session: true,
34
+ },
35
+ intl: {
36
+ locale: 'en',
37
+ messages: {},
38
+ formatMessage: jest.fn(),
39
+ },
40
+ });
41
+
42
+ render(
43
+ <Provider store={store}>
44
+ <Login />
45
+ </Provider>,
46
+ );
47
+
48
+ expect(screen.getByText('Loading')).toBeInTheDocument();
49
+ });
50
+
51
+ it('dispatches listAuthOptions on mount', () => {
52
+ const store = mockStore({
53
+ authOptions: { loading: true, options: [] },
54
+ authomaticRedirect: {
55
+ next_url: '/dashboard',
56
+ session: true,
57
+ },
58
+ oidcRedirect: {
59
+ next_url: '/dashboard',
60
+ session: true,
61
+ },
62
+ intl: {
63
+ locale: 'en',
64
+ messages: {},
65
+ formatMessage: jest.fn(),
66
+ },
67
+ });
68
+ const dispatch = jest.spyOn(store, 'dispatch');
69
+
70
+ render(
71
+ <Provider store={store}>
72
+ <Login />
73
+ </Provider>,
74
+ );
75
+
76
+ expect(dispatch).toHaveBeenCalledWith(listAuthOptions());
77
+ });
78
+
79
+ it('renders providers from the store', () => {
80
+ const store = mockStore({
81
+ authOptions: {
82
+ loading: false,
83
+ options: [
84
+ { id: 'facebook', title: 'Facebook' },
85
+ { id: 'twitter', title: 'Twitter' },
86
+ ],
87
+ },
88
+ authomaticRedirect: {
89
+ next_url: '/dashboard',
90
+ session: true,
91
+ },
92
+ oidcRedirect: {
93
+ next_url: '/dashboard',
94
+ session: true,
95
+ },
96
+ intl: {
97
+ locale: 'en',
98
+ messages: {},
99
+ formatMessage: jest.fn(),
100
+ },
101
+ });
102
+
103
+ render(
104
+ <Provider store={store}>
105
+ <Login />
106
+ </Provider>,
107
+ );
108
+
109
+ // Assert that the provider buttons are present
110
+ expect(
111
+ screen.getByRole('button', { name: /Facebook/i }),
112
+ ).toBeInTheDocument();
113
+ expect(
114
+ screen.getByRole('button', { name: /Twitter/i }),
115
+ ).toBeInTheDocument();
116
+
117
+ fireEvent.click(screen.getByRole('button', { name: /Facebook/i }));
118
+ });
119
+
120
+ it('clicks on GitHub button for OAuth login', () => {
121
+ useLocation.mockReturnValue({ pathname: '/login' });
122
+
123
+ const store = mockStore({
124
+ authOptions: {
125
+ loading: false,
126
+ options: [{ id: 'github', title: 'GitHub' }],
127
+ },
128
+ authomaticRedirect: { next_url: '/dashboard', session: true },
129
+ oidcRedirect: {
130
+ next_url: '/dashboard',
131
+ session: true,
132
+ },
133
+ intl: {
134
+ locale: 'en',
135
+ messages: {},
136
+ formatMessage: jest.fn(),
137
+ },
138
+ });
139
+
140
+ render(
141
+ <Provider store={store}>
142
+ <Login />
143
+ </Provider>,
144
+ );
145
+
146
+ fireEvent.click(screen.getByRole('button', { name: /GitHub/i }));
147
+ });
148
+
149
+ it('renders correctly with loading state', () => {
150
+ const store = mockStore({
151
+ authOptions: {
152
+ loading: true,
153
+ options: [{ id: 'oidc', title: 'OIDC' }],
154
+ },
155
+ authomaticRedirect: {
156
+ next_url: '/dashboard',
157
+ session: true,
158
+ },
159
+ oidcRedirect: {
160
+ next_url: '/dashboard',
161
+ session: true,
162
+ },
163
+ intl: {
164
+ locale: 'en',
165
+ messages: {},
166
+ formatMessage: jest.fn(),
167
+ },
168
+ });
169
+
170
+ render(
171
+ <Provider store={store}>
172
+ <Login />
173
+ </Provider>,
174
+ );
175
+
176
+ expect(screen.getByText('Loading')).toBeInTheDocument();
177
+ jest.advanceTimersByTime(500);
178
+ });
179
+ });
package/src/index.js CHANGED
@@ -2,6 +2,9 @@ import { runtimeConfig } from '@plone/volto/runtime_config';
2
2
  import installContextNavigationBlock from '@eeacms/volto-eea-website-policy/components/Blocks/ContextNavigation';
3
3
  import { appendGroup } from './helpers';
4
4
  import { FrequencyOfDissemination } from '@eeacms/volto-eea-website-policy/components/Widgets/FrequencyOfDissemination';
5
+ import Login from '@eeacms/volto-eea-website-policy/components/AzureLogin/Login.jsx';
6
+ import Logout from '@plone-collective/volto-authomatic/components/Logout/Logout.jsx';
7
+ import { Login as VoltoLogin } from '@plone/volto/components';
5
8
 
6
9
  const restrictedBlocks = [
7
10
  'imagecards',
@@ -40,7 +43,7 @@ const overrideBlocks = {
40
43
  const applyConfig = (config) => {
41
44
  // #158717#note-25 any path that isn't static, en or controlpanel is treated as external
42
45
  const notInEN =
43
- /^(?!(#|\/en|\/login-authomatic|\/fallback_login|\/static|\/controlpanel|\/cypress|\/login|\/logout|\/contact-form|\/passwordreset)).*$/;
46
+ /^(?!(#|\/en|\/login-authomatic|\/azure_login|\/fallback_login|\/static|\/controlpanel|\/cypress|\/login|\/logout|\/contact-form|\/passwordreset)).*$/;
44
47
  config.settings.externalRoutes = [
45
48
  ...(config.settings.externalRoutes || []),
46
49
  {
@@ -52,6 +55,17 @@ const applyConfig = (config) => {
52
55
  },
53
56
  ];
54
57
 
58
+ // #258877 Make Azure AD login to be at route /azure_login and EIONET LDAP login to be at /login
59
+ config.addonRoutes = [
60
+ { path: '/azure_login', component: Login },
61
+ { path: '/**/azure_login', component: Login },
62
+ { path: '/login', component: VoltoLogin },
63
+ { path: '/**/login', component: VoltoLogin },
64
+ { path: '/logout', component: Logout },
65
+ { path: '/**/logout', component: Logout },
66
+ ...(config.addonRoutes || []),
67
+ ];
68
+
55
69
  // #160689 Redirect contact-form to contact-us
56
70
  config.settings.contactForm = '/en/about/contact-us';
57
71