Skip to content

Commit

Permalink
Merge pull request #443 from kbss-cvut/development
Browse files Browse the repository at this point in the history
[3.0.2] Release
  • Loading branch information
ledsoft authored Nov 15, 2023
2 parents 11ffe8e + 3d0c3b0 commit 82158a8
Show file tree
Hide file tree
Showing 18 changed files with 445 additions and 211 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ REACT_APP_DEPLOYMENT_NAME=
REACT_APP_AUTHENTICATION=
REACT_APP_AUTH_SERVER_URL=
REACT_APP_AUTH_CLIENT_ID=
REACT_APP_AUTH_SERVER_MANAGEMENT=
REACT_APP_AUTH_SERVER_USER_PROFILE=
5 changes: 5 additions & 0 deletions NEWS.cs.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#### Verze 3.0.2

- Přidána podpora pro nasazení s externí autorizační službou.
- Přidána možnost specifikovat soubor s taxonomií stavů/typů pojmů.

#### Verze 3.0.1

- Opraven problém s nastavováním pojmů se stejným významem.
Expand Down
5 changes: 5 additions & 0 deletions NEWS.en.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#### Version 3.0.2

- Added support for running with an external authentication service.
- Added support for providing files with custom term states/types taxonomies.

#### Version 3.0.1

- Fixed an issue with setting exact match terms.
Expand Down
479 changes: 303 additions & 176 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "termit-ui",
"version": "3.0.1",
"version": "3.0.2",
"private": true,
"homepage": ".",
"license": "GPL-3.0-only",
Expand All @@ -10,7 +10,7 @@
"@fortawesome/fontawesome-free": "^6.4.0",
"@opendata-mvcr/assembly-line-shared": "^0.3.2",
"apexcharts": "^3.41.0",
"axios": "^0.27.2",
"axios": "^1.6.2",
"bootstrap": "4.6.2",
"chart.js": "^3.9.1",
"classnames": "^2.3.2",
Expand Down Expand Up @@ -94,7 +94,7 @@
"@types/uuid": "^9.0.2",
"@types/whatwg-mimetype": "^2.1.1",
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
"axios-mock-adapter": "^1.21.4",
"axios-mock-adapter": "^1.22.0",
"babel-plugin-macros": "3.1.0",
"cross-env": "^7.0.3",
"enzyme": "^3.11.0",
Expand Down Expand Up @@ -129,6 +129,9 @@
]
},
"jest": {
"resetMocks": false
"resetMocks": false,
"transformIgnorePatterns": [
"node_modules/(?!axios)/"
]
}
}
4 changes: 3 additions & 1 deletion public/config.js.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ window.__config__ = {
SHOW_PUBLIC_VIEW_ON_UNAUTHORIZED: '${SHOW_PUBLIC_VIEW_ON_UNAUTHORIZED}',
AUTHENTICATION: '${AUTHENTICATION}',
AUTH_SERVER_URL: '${AUTH_SERVER_URL}',
AUTH_CLIENT_ID: '${AUTH_CLIENT_ID}'
AUTH_CLIENT_ID: '${AUTH_CLIENT_ID}',
AUTH_SERVER_MANAGEMENT: '${AUTH_SERVER_MANAGEMENT}',
AUTH_SERVER_USER_PROFILE: '${AUTH_SERVER_USER_PROFILE}'
}
15 changes: 15 additions & 0 deletions src/component/administration/user/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import { useI18n } from "../../hook/useI18n";
import PromiseTrackingMask from "../../misc/PromiseTrackingMask";
import { trackPromise } from "react-promise-tracker";
import IfInternalAuth from "../../misc/oidc/IfInternalAuth";
import { isUsingOidcAuth } from "../../../util/OidcUtils";
import IfOidcAuth from "../../misc/oidc/IfOidcAuth";
import { getEnv } from "../../../util/Constants";
import ConfigParam from "../../../util/ConfigParam";
import OutgoingLink from "../../misc/OutgoingLink";

const Users: React.FC = () => {
const { i18n } = useI18n();
Expand Down Expand Up @@ -88,13 +93,23 @@ const Users: React.FC = () => {
onSubmit={onUserRoleSubmit}
onCancel={() => setEditedUser(EMPTY_USER)}
/>
<IfOidcAuth>
<p id="oidc-notice" className="italics">
<OutgoingLink
iri={getEnv(ConfigParam.AUTH_SERVER_MANAGEMENT_URL, "")}
label={i18n("administration.users.oidc")}
showLink={true}
/>
</p>
</IfOidcAuth>
<UsersTable
users={users}
currentUser={currentUser}
disable={onDisableUser}
enable={onEnableUser}
unlock={(user) => setUserToUnlock(user)}
changeRole={(user) => setEditedUser(user)}
readOnly={isUsingOidcAuth()}
/>
</PanelWithActions>
</>
Expand Down
27 changes: 15 additions & 12 deletions src/component/administration/user/UsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import classNames from "classnames";
interface UsersTableProps extends UserActions {
users: User[];
currentUser: User;
readOnly?: boolean;
}

const UsersTable: React.FC<UsersTableProps> = (props) => {
const { users, currentUser, disable, enable, unlock, changeRole } = props;
const { users, currentUser, disable, enable, unlock, changeRole, readOnly } =
props;
const { i18n } = useI18n();
const data = React.useMemo(() => users, [users]);
const columns: Column<User>[] = React.useMemo(
Expand Down Expand Up @@ -77,20 +79,21 @@ const UsersTable: React.FC<UsersTableProps> = (props) => {
disableFilters: true,
disableSortBy: true,
// @ts-ignore
Cell: ({ row }) => (
<UserActionsButtons
user={row.original}
currentUser={currentUser}
disable={disable}
enable={enable}
unlock={unlock}
changeRole={changeRole}
/>
),
Cell: ({ row }) =>
!readOnly && (
<UserActionsButtons
user={row.original}
currentUser={currentUser}
disable={disable}
enable={enable}
unlock={unlock}
changeRole={changeRole}
/>
),
className: "align-middle table-row-actions",
},
],
[i18n, disable, enable, unlock, changeRole, currentUser]
[i18n, disable, enable, unlock, changeRole, currentUser, readOnly]
);
const filterTypes = React.useMemo(() => ({ text: textContainsFilter }), []);
const tableInstance = useTable<User>(
Expand Down
18 changes: 18 additions & 0 deletions src/component/administration/user/__tests__/Users.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import * as UserActions from "../../../../action/AsyncUserActions";
import UsersTable from "../UsersTable";
import { mockUseI18n } from "../../../../__tests__/environment/IntlUtil";
import * as Redux from "react-redux";
import * as OidcUtils from "../../../../util/OidcUtils";
import * as Constats from "../../../../util/Constants";

jest.mock("react-redux", () => ({
...jest.requireActual("react-redux"),
Expand Down Expand Up @@ -61,6 +63,22 @@ describe("Users", () => {
});
});

it("renders users table read only when using OIDC authentication", () => {
jest.spyOn(OidcUtils, "isUsingOidcAuth").mockReturnValue(true);
jest.spyOn(UserActions, "loadUsers");
const wrapper = render();
expect(wrapper.find(UsersTable).prop("readOnly")).toBeTruthy();
});

it("renders link to auth service administration when using OIDC authentication", () => {
const link = "http://localhost/services/auth";
jest.spyOn(Constats, "getEnv").mockReturnValue(link);
jest.spyOn(OidcUtils, "isUsingOidcAuth").mockReturnValue(true);
jest.spyOn(UserActions, "loadUsers");
const wrapper = render();
expect(wrapper.exists("#oidc-notice")).toBeTruthy();
});

describe("user unlocking", () => {
it("opens unlock user dialog and passes selected user to it on unlock click", () => {
const wrapper = render();
Expand Down
5 changes: 1 addition & 4 deletions src/component/misc/oidc/IfInternalAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import React from "react";
import { isUsingOidcAuth } from "../../../util/OidcUtils";

const IfInternalAuth: React.FC = ({ children }) => {
if (isUsingOidcAuth()) {
return null;
}
return <>{children}</>;
return isUsingOidcAuth() ? null : <>{children}</>;
};

export default IfInternalAuth;
8 changes: 8 additions & 0 deletions src/component/misc/oidc/IfOidcAuth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from "react";
import { isUsingOidcAuth } from "../../../util/OidcUtils";

const IfOidcAuth: React.FC = ({ children }) => {
return isUsingOidcAuth() ? <>{children}</> : null;
};

export default IfOidcAuth;
13 changes: 13 additions & 0 deletions src/component/profile/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import { Card, CardBody } from "reactstrap";
import WindowTitle from "../misc/WindowTitle";
import UserRoles from "../administration/user/UserRoles";
import IfInternalAuth from "../misc/oidc/IfInternalAuth";
import { getEnv } from "../../util/Constants";
import ConfigParam from "../../util/ConfigParam";
import IfOidcAuth from "../misc/oidc/IfOidcAuth";
import OutgoingLink from "../misc/OutgoingLink";

interface ProfileProps extends HasI18n {
user: User;
Expand Down Expand Up @@ -108,6 +112,15 @@ export class Profile extends React.Component<ProfileProps, ProfileState> {
/>
<Card id="panel-profile">
<CardBody>
<IfOidcAuth>
<p id="oidc-notice" className="italics">
<OutgoingLink
iri={getEnv(ConfigParam.AUTH_SERVER_USER_PROFILE_URL, "")}
label={i18n("administration.users.oidc")}
showLink={true}
/>
</p>
</IfOidcAuth>
{!this.state.edit ? (
<ProfileView user={user} />
) : (
Expand Down
20 changes: 20 additions & 0 deletions src/component/profile/__tests__/Profile.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import Generator from "../../../__tests__/environment/Generator";
import { intlFunctions } from "../../../__tests__/environment/IntlUtil";
import ProfileEditForm from "../ProfileEditForm";
import HeaderWithActions from "../../misc/HeaderWithActions";
import * as OidcUtils from "../../../util/OidcUtils";
import * as Constats from "../../../util/Constants";

describe("Profile", () => {
let updateProfile: (user: User) => Promise<AsyncAction>;
Expand Down Expand Up @@ -47,4 +49,22 @@ describe("Profile", () => {
expect(actionButtons.length).toEqual(1);
expect(wrapper.find(ProfileEditForm).length).toEqual(1);
});

it("does not render edit buttons when using OIDC authentication", () => {
jest.spyOn(OidcUtils, "isUsingOidcAuth").mockReturnValue(true);
const wrapper = mountWithIntl(
<Profile updateProfile={updateProfile} user={user} {...intlFunctions()} />
);
expect(wrapper.exists(ProfileActionButtons)).toBeFalsy();
});

it("renders link to user profile in auth service when using OIDC authentication", () => {
const link = "http://localhost/services/auth/profile";
jest.spyOn(Constats, "getEnv").mockReturnValue(link);
jest.spyOn(OidcUtils, "isUsingOidcAuth").mockReturnValue(true);
const wrapper = mountWithIntl(
<Profile updateProfile={updateProfile} user={user} {...intlFunctions()} />
);
expect(wrapper.exists("#oidc-notice")).toBeTruthy();
});
});
2 changes: 2 additions & 0 deletions src/i18n/cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ const cs = {
'Uživatel "{name}" úspěšně vytvořen.',
"administration.users.you": "Toto je Váš účet",
"administration.users.types.admin": "Tento uživatel je administrátor",
"administration.users.oidc":
"Pro správu uživatelů je využívána externí autentizační služba.",
"administration.maintenance.title": "Správa systému",
"administration.maintenance.invalidateCaches": "Vyprázdnit cache",
"administration.maintenance.invalidateCaches.tooltip":
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ const en = {
'User "{name}" successfully created.',
"administration.users.you": "This is you",
"administration.users.types.admin": "This user is an administrator",
"administration.users.oidc":
"An external authentication service is used to management users.",
"administration.maintenance.title": "Maintenance",
"administration.maintenance.invalidateCaches": "Invalidate caches",
"administration.maintenance.invalidateCaches.tooltip":
Expand Down
16 changes: 10 additions & 6 deletions src/util/Ajax.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import axios, { AxiosInstance, AxiosRequestConfig, ResponseType } from "axios";
import axios, {
AxiosHeaders,
AxiosInstance,
AxiosRequestConfig,
ResponseType,
} from "axios";
import Routing from "./Routing";
import Constants, { getEnv } from "./Constants";
import Routes from "./Routes";
Expand Down Expand Up @@ -189,18 +194,17 @@ export class Ajax {
constructor() {
this.axiosInstance.interceptors.request.use((reqConfig) => {
if (!reqConfig.headers) {
reqConfig.headers = {};
reqConfig.headers = new AxiosHeaders();
}
reqConfig.headers[Constants.Headers.AUTHORIZATION] =
SecurityUtils.loadToken();
reqConfig.headers.setAuthorization(SecurityUtils.loadToken());
reqConfig.withCredentials = true;
return reqConfig;
});
this.axiosInstance.interceptors.response.use(
(resp) => {
if (resp.headers && resp.headers[Constants.Headers.AUTHORIZATION]) {
if (resp.headers && (resp.headers as AxiosHeaders).hasAuthorization()) {
SecurityUtils.saveToken(
resp.headers[Constants.Headers.AUTHORIZATION]
(resp.headers as AxiosHeaders).getAuthorization() as any
);
}
return resp;
Expand Down
2 changes: 2 additions & 0 deletions src/util/ConfigParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const ConfigParam = {
SHOW_PUBLIC_VIEW_ON_UNAUTHORIZED: "SHOW_PUBLIC_VIEW_ON_UNAUTHORIZED",
MOCK_REST_API: "MOCK_REST_API",
AUTH_TYPE: "AUTHENTICATION",
AUTH_SERVER_MANAGEMENT_URL: "AUTH_SERVER_MANAGEMENT",
AUTH_SERVER_USER_PROFILE_URL: "AUTH_SERVER_USER_PROFILE",
};

export default ConfigParam;
22 changes: 14 additions & 8 deletions src/util/__tests__/Ajax.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ import {
} from "../Ajax";
import Routing from "../Routing";
import { EMPTY_USER } from "../../model/User";
import Constants from "../Constants";
import * as Const from "../Constants";
import Constants, * as Const from "../Constants";
import Routes from "../Routes";
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import {
AxiosHeaders,
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
} from "axios";
import { ErrorData } from "../../model/ErrorInfo";
import SecurityUtils from "../SecurityUtils";
import VocabularyUtils from "../VocabularyUtils";
Expand All @@ -34,12 +38,12 @@ describe("Ajax", () => {
const mock = new MockAdapter(sut.axios);
const jwt = "12345";

let headers: {};
let headers: AxiosHeaders;

beforeEach(() => {
mock.reset();
headers = {};
headers[Constants.Headers.AUTHORIZATION] = jwt;
headers = new AxiosHeaders();
headers.setAuthorization(jwt);
});

afterEach(() => {
Expand All @@ -49,14 +53,16 @@ describe("Ajax", () => {
it("loads JWT and sets it on request", () => {
SecurityUtils.loadToken = jest.fn().mockReturnValue(jwt);
mock.onGet("/users/current").reply((config: AxiosRequestConfig) => {
expect(config.headers![Constants.Headers.AUTHORIZATION]).toContain(jwt);
expect((config.headers as AxiosHeaders).getAuthorization()).toContain(
jwt
);
return [200, require("../../rest-mock/current"), headers];
});
return sut.get("/users/current");
});

it("extracts current JWT from response and saves it using Authentication", () => {
headers[Constants.Headers.AUTHORIZATION] = jwt;
headers.setAuthorization(jwt);
SecurityUtils.saveToken = jest.fn();
mock
.onGet("/users/current")
Expand Down

0 comments on commit 82158a8

Please sign in to comment.