diff --git a/databox/api/composer.lock b/databox/api/composer.lock index bdd174a08..8cb6a94c3 100644 --- a/databox/api/composer.lock +++ b/databox/api/composer.lock @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/alchemy-fr/acl-bundle.git", - "reference": "c6f19b803b9afe749e5f90ece5b7e45751b5bd71" + "reference": "5a0316c05d67e0674674a87b1b007453b3d13ce8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alchemy-fr/acl-bundle/zipball/c6f19b803b9afe749e5f90ece5b7e45751b5bd71", - "reference": "c6f19b803b9afe749e5f90ece5b7e45751b5bd71", + "url": "https://api.github.com/repos/alchemy-fr/acl-bundle/zipball/5a0316c05d67e0674674a87b1b007453b3d13ce8", + "reference": "5a0316c05d67e0674674a87b1b007453b3d13ce8", "shasum": "" }, "require": { @@ -28,6 +28,7 @@ "symfony/event-dispatcher": "^4.0|^5.4|^6.1", "symfony/framework-bundle": "^4.0|^5.4|^6.1", "symfony/security-bundle": "^4.0|^5.4|^6.1", + "symfony/validator": "^6.3", "symfony/yaml": "^4.4|^5.4|^6.1" }, "require-dev": { @@ -55,7 +56,7 @@ "issues": "https://github.com/alchemy-fr/acl-bundle/issues", "source": "https://github.com/alchemy-fr/acl-bundle/tree/main" }, - "time": "2023-08-01T07:48:32+00:00" + "time": "2023-10-02T12:29:19+00:00" }, { "name": "alchemy/admin-bundle", diff --git a/databox/api/src/Api/Model/Output/AssetOutput.php b/databox/api/src/Api/Model/Output/AssetOutput.php index 6b38b0da5..9dc60513e 100644 --- a/databox/api/src/Api/Model/Output/AssetOutput.php +++ b/databox/api/src/Api/Model/Output/AssetOutput.php @@ -62,6 +62,9 @@ class AssetOutput extends AbstractUuidOutput #[Groups([Asset::GROUP_LIST, Asset::GROUP_READ])] private array $tags; + #[Groups([Asset::GROUP_READ])] + public ?UserOutput $owner = null; + #[Groups([Asset::GROUP_LIST, Asset::GROUP_READ])] private array $collections; diff --git a/databox/api/src/Api/Model/Output/CollectionOutput.php b/databox/api/src/Api/Model/Output/CollectionOutput.php index 36b5b90cd..66e8a452e 100644 --- a/databox/api/src/Api/Model/Output/CollectionOutput.php +++ b/databox/api/src/Api/Model/Output/CollectionOutput.php @@ -37,6 +37,9 @@ class CollectionOutput extends AbstractUuidOutput #[Groups([Collection::GROUP_LIST, Collection::GROUP_READ, Workspace::GROUP_LIST, Workspace::GROUP_READ])] private ?string $ownerId = null; + #[Groups([Collection::GROUP_READ])] + public ?UserOutput $owner = null; + #[Groups([Collection::GROUP_LIST, Collection::GROUP_READ, Workspace::GROUP_LIST, Workspace::GROUP_READ])] private int $privacy; diff --git a/databox/api/src/Api/Model/Output/UserOutput.php b/databox/api/src/Api/Model/Output/UserOutput.php new file mode 100644 index 000000000..88aa50544 --- /dev/null +++ b/databox/api/src/Api/Model/Output/UserOutput.php @@ -0,0 +1,16 @@ +getElasticHighlights(); + if ($this->hasGroup([ + Asset::GROUP_READ, + ], $context)) { + $output->owner = $this->transformUser($data->getOwnerId()); + } + if ($this->hasGroup([ Asset::GROUP_LIST, Asset::GROUP_READ, diff --git a/databox/api/src/Api/OutputTransformer/CollectionOutputTransformer.php b/databox/api/src/Api/OutputTransformer/CollectionOutputTransformer.php index ecb9239c2..ce340157d 100644 --- a/databox/api/src/Api/OutputTransformer/CollectionOutputTransformer.php +++ b/databox/api/src/Api/OutputTransformer/CollectionOutputTransformer.php @@ -14,6 +14,7 @@ class CollectionOutputTransformer implements OutputTransformerInterface { use GroupsHelperTrait; + use UserOutputTransformerTrait; use SecurityAwareTrait; public function __construct( @@ -39,6 +40,12 @@ public function transform($data, string $outputClass, array &$context = []): obj $output->setPrivacy($data->getPrivacy()); $output->setWorkspace($data->getWorkspace()); + if ($this->hasGroup([ + Collection::GROUP_READ, + ], $context)) { + $output->owner = $this->transformUser($data->getOwnerId()); + } + if ($this->hasGroup(Collection::GROUP_CHILDREN, $context)) { $maxChildrenLimit = 30; if (preg_match('#[&?]childrenLimit=(\d+)#', (string) $context['request_uri'], $regs)) { diff --git a/databox/api/src/Api/OutputTransformer/UserOutputTransformerTrait.php b/databox/api/src/Api/OutputTransformer/UserOutputTransformerTrait.php new file mode 100644 index 000000000..43ff2f059 --- /dev/null +++ b/databox/api/src/Api/OutputTransformer/UserOutputTransformerTrait.php @@ -0,0 +1,37 @@ +id = $userId; + + $user = $this->userRepository->getUser($userId); + if (null !== $user) { + $output->username = $user['username']; + } + + return $output; + } + + #[Required] + public function setUserRepository(UserRepositoryInterface $userRepository): void + { + $this->userRepository = $userRepository; + } +} diff --git a/databox/api/src/Entity/Core/Asset.php b/databox/api/src/Entity/Core/Asset.php index bd74a1786..2ec07b289 100644 --- a/databox/api/src/Entity/Core/Asset.php +++ b/databox/api/src/Entity/Core/Asset.php @@ -50,7 +50,11 @@ #[ApiResource( shortName: 'asset', operations: [ - new Get(), + new Get( + normalizationContext: [ + 'groups' => [Asset::GROUP_READ], + ] + ), new Delete(security: 'is_granted("DELETE", object)'), new Put(security: 'is_granted("EDIT", object)'), new Patch(security: 'is_granted("EDIT", object)'), @@ -176,6 +180,9 @@ class Asset extends AbstractUuidEntity implements HighlightableModelInterface, W #[ORM\OneToMany(mappedBy: 'asset', targetEntity: AssetRendition::class, cascade: ['remove'])] private ?DoctrineCollection $renditions = null; + #[ORM\OneToMany(mappedBy: 'asset', targetEntity: AssetFileVersion::class, cascade: ['remove'])] + private ?DoctrineCollection $fileVersions = null; + private ?array $highlights = null; /** @@ -202,6 +209,7 @@ public function __construct(float $now = null, int $sequence = null) $this->renditions = new ArrayCollection(); $this->tags = new ArrayCollection(); $this->attributes = new ArrayCollection(); + $this->fileVersions = new ArrayCollection(); /* @var $now float */ $now ??= microtime(true); diff --git a/databox/client/package.json b/databox/client/package.json index 6c2b852ee..13d471750 100644 --- a/databox/client/package.json +++ b/databox/client/package.json @@ -51,7 +51,7 @@ "react-pdf": "^5.7.2", "react-player": "^2.10.1", "@alchemy/auth": "link:./__lib/auth", - "react-ps": "link:./__lib/react-ps", + "@alchemy/react-ps": "link:./__lib/react-ps", "react-router-dom": "6", "react-scripts": "^5.0.1", "react-select": "^5.3.2", diff --git a/databox/client/src/api/user.ts b/databox/client/src/api/user.ts index 3748f093d..1ea3fc4f9 100644 --- a/databox/client/src/api/user.ts +++ b/databox/client/src/api/user.ts @@ -1,6 +1,5 @@ import apiClient from "./api-client"; import {Group, User} from "../types"; -import config from "../config"; import {UserPreferences} from "../components/User/Preferences/UserPreferencesContext"; export async function getUsers(): Promise { diff --git a/databox/client/src/components/Dialog/Asset/InfoAsset.tsx b/databox/client/src/components/Dialog/Asset/InfoAsset.tsx index c496aec48..56e6ad92e 100644 --- a/databox/client/src/components/Dialog/Asset/InfoAsset.tsx +++ b/databox/client/src/components/Dialog/Asset/InfoAsset.tsx @@ -5,6 +5,7 @@ import ContentTab from "../Tabbed/ContentTab"; import {Divider, MenuList} from "@mui/material"; import KeyIcon from '@mui/icons-material/Key'; import EventIcon from '@mui/icons-material/Event'; +import PersonIcon from '@mui/icons-material/Person'; import InfoRow from "../Info/InfoRow"; type Props = { @@ -28,6 +29,12 @@ export default function InfoAsset({ icon={} /> + } + /> } /> + } + /> (); + const [user, setUser] = React.useState(); React.useEffect(() => { const onLogin = async () => { diff --git a/databox/client/src/components/Security/UserContext.ts b/databox/client/src/components/Security/UserContext.ts index f7db93fa7..a9497bc5a 100644 --- a/databox/client/src/components/Security/UserContext.ts +++ b/databox/client/src/components/Security/UserContext.ts @@ -1,8 +1,8 @@ import React from "react"; -import {User} from "../../types"; +import {AuthUser} from "../../types"; export type TUserContext = { - user?: User | undefined; + user?: AuthUser | undefined; logout?: (redirectUri?: string |false) => void | undefined; } diff --git a/databox/client/src/types.ts b/databox/client/src/types.ts index 693885d32..0ea54e897 100644 --- a/databox/client/src/types.ts +++ b/databox/client/src/types.ts @@ -23,6 +23,11 @@ type GroupValue = { type: AttributeType; } +export type User = { + id: string; + username: string; +} + export interface Asset extends IPermissions<{ canEditAttributes: boolean; canShare: boolean; @@ -34,6 +39,7 @@ export interface Asset extends IPermissions<{ description?: string; privacy: number; tags: Tag[]; + owner?: User; workspace: Workspace; attributes: Attribute[]; collections: Collection[]; @@ -169,12 +175,10 @@ export interface Tag extends ApiHydraObjectResponse { workspace: Workspace | string; } -export interface User { - id: string; - username: string; +export type AuthUser = { roles: string[]; groups: string[]; -} +} & User; export interface Group { id: string; @@ -192,6 +196,7 @@ export interface Collection extends IPermissions { privacy: number; createdAt: string; updatedAt: string; + owner?: User; } export interface Workspace extends IPermissions { diff --git a/databox/client/yarn.lock b/databox/client/yarn.lock index 4ce0e793b..1be49d71d 100644 --- a/databox/client/yarn.lock +++ b/databox/client/yarn.lock @@ -6,6 +6,10 @@ version "0.0.0" uid "" +"@alchemy/react-ps@link:./__lib/react-ps": + version "0.0.0" + uid "" + "@alchemy/visual-workflow@link:./__lib/visual-workflow": version "0.0.0" uid "" @@ -9192,10 +9196,6 @@ react-player@^2.10.1: prop-types "^15.7.2" react-fast-compare "^3.0.1" -"react-ps@link:./__lib/react-ps": - version "0.0.0" - uid "" - react-refresh@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 66dc34cfb..7ee31b58c 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -186,6 +186,7 @@ services: - ./:/var/workspace - ${SSH_AUTH_SOCK}:/ssh-auth-sock - ${HOME}/.ssh:/home/app/.ssh + - ${HOME}/.composer:/home/app/.composer - dev_vol:/home/app - ./configs:/configs extra_hosts: diff --git a/expose/api/composer.lock b/expose/api/composer.lock index c59d6702d..c540f243f 100644 --- a/expose/api/composer.lock +++ b/expose/api/composer.lock @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/alchemy-fr/acl-bundle.git", - "reference": "c6f19b803b9afe749e5f90ece5b7e45751b5bd71" + "reference": "5a0316c05d67e0674674a87b1b007453b3d13ce8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alchemy-fr/acl-bundle/zipball/c6f19b803b9afe749e5f90ece5b7e45751b5bd71", - "reference": "c6f19b803b9afe749e5f90ece5b7e45751b5bd71", + "url": "https://api.github.com/repos/alchemy-fr/acl-bundle/zipball/5a0316c05d67e0674674a87b1b007453b3d13ce8", + "reference": "5a0316c05d67e0674674a87b1b007453b3d13ce8", "shasum": "" }, "require": { @@ -28,6 +28,7 @@ "symfony/event-dispatcher": "^4.0|^5.4|^6.1", "symfony/framework-bundle": "^4.0|^5.4|^6.1", "symfony/security-bundle": "^4.0|^5.4|^6.1", + "symfony/validator": "^6.3", "symfony/yaml": "^4.4|^5.4|^6.1" }, "require-dev": { @@ -55,7 +56,7 @@ "issues": "https://github.com/alchemy-fr/acl-bundle/issues", "source": "https://github.com/alchemy-fr/acl-bundle/tree/main" }, - "time": "2023-08-01T07:48:32+00:00" + "time": "2023-10-02T12:29:19+00:00" }, { "name": "alchemy/admin-bundle", diff --git a/expose/client/package.json b/expose/client/package.json index 14c705aef..f8c6fe750 100644 --- a/expose/client/package.json +++ b/expose/client/package.json @@ -26,7 +26,7 @@ "react-images": "^1.1.7", "react-loader-spinner": "^3.1.4", "@alchemy/auth": "link:./__lib/auth", - "react-ps": "link:./__lib/react-ps", + "@alchemy/react-ps": "link:./__lib/react-ps", "react-router-dom": "^5.1.2", "react-scripts": "3.2.0", "sass": "^1.44.0", diff --git a/expose/client/src/component/App.js b/expose/client/src/component/App.js index 92b71327e..3239c3e98 100644 --- a/expose/client/src/component/App.js +++ b/expose/client/src/component/App.js @@ -7,7 +7,7 @@ import {getAuthRedirect, unsetAuthRedirect} from "../lib/oauth"; import config from "../lib/config"; import ErrorPage from "./ErrorPage"; import OAuthRedirect from "./OAuthRedirect"; -import {DashboardMenu} from "react-ps"; +import {DashboardMenu} from "@alchemy/react-ps"; import EmbeddedAsset from "./EmbeddedAsset"; import {oauthClient} from "../lib/api-client"; diff --git a/expose/client/src/component/OAuthRedirect.tsx b/expose/client/src/component/OAuthRedirect.tsx index 4a3100b87..3354f0175 100644 --- a/expose/client/src/component/OAuthRedirect.tsx +++ b/expose/client/src/component/OAuthRedirect.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {useEffectOnce} from "react-ps"; +import {useEffectOnce} from "@alchemy/react-ps"; import {OAuthClient} from "@alchemy/auth"; import qs from "querystring"; import {useHistory, useLocation} from "react-router-dom"; diff --git a/expose/client/yarn.lock b/expose/client/yarn.lock index cf9d225a7..2d23d31a5 100644 --- a/expose/client/yarn.lock +++ b/expose/client/yarn.lock @@ -6,6 +6,10 @@ version "0.0.0" uid "" +"@alchemy/react-ps@link:./__lib/react-ps": + version "0.0.0" + uid "" + "@ampproject/remapping@^2.1.0": version "2.1.2" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" @@ -9863,10 +9867,6 @@ react-property@1.0.1: resolved "https://registry.yarnpkg.com/react-property/-/react-property-1.0.1.tgz#4ae4211557d0a0ae050a71aa8ad288c074bea4e6" integrity sha512-1tKOwxFn3dXVomH6pM9IkLkq2Y8oh+fh/lYW3MJ/B03URswUTqttgckOlbxY2XHF3vPG6uanSc4dVsLW/wk3wQ== -"react-ps@link:./__lib/react-ps": - version "0.0.0" - uid "" - react-remove-scroll-bar@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz#d4d545a7df024f75d67e151499a6ab5ac97c8cdd" diff --git a/lib/js/react-ps/package.json b/lib/js/react-ps/package.json index a3b601bd5..154243eae 100644 --- a/lib/js/react-ps/package.json +++ b/lib/js/react-ps/package.json @@ -1,5 +1,5 @@ { - "name": "react-ps", + "name": "@alchemy/react-ps", "version": "0.0.0", "description": "Phrasea React components", "keywords": [], diff --git a/lib/js/react-ps/src/index.ts b/lib/js/react-ps/src/index.ts index 341f3d40e..25f4bd9e4 100644 --- a/lib/js/react-ps/src/index.ts +++ b/lib/js/react-ps/src/index.ts @@ -1,5 +1,4 @@ import DashboardMenu from "./components/DashboardMenu/DashboardMenu"; - import useEffectOnce from "./hooks/useEffectOnce"; export { diff --git a/lib/js/react-ps/tsconfig.json b/lib/js/react-ps/tsconfig.json index 671eab6ad..c6633d8b8 100644 --- a/lib/js/react-ps/tsconfig.json +++ b/lib/js/react-ps/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "moduleResolution": "node", "target": "es5", - "module": "commonjs", + "module": "esnext", "lib": ["es2015", "es2016", "es2017", "dom"], "strict": true, "sourceMap": true, diff --git a/lib/php/auth-bundle/Client/KeycloakClient.php b/lib/php/auth-bundle/Client/KeycloakClient.php index 0896995fa..75551e4fb 100644 --- a/lib/php/auth-bundle/Client/KeycloakClient.php +++ b/lib/php/auth-bundle/Client/KeycloakClient.php @@ -108,6 +108,17 @@ public function createUser(array $data, string $accessToken): array return $users[0]; } + public function getUser(string $accessToken, string $userId, array $options = []): ?array + { + return $this->wrapRequest(function () use ($userId, $accessToken, $options) { + return $this->keycloakClient->request('GET', sprintf('%s/%s', $this->urlGenerator->getUsersApiUrl(), $userId), array_merge([ + 'headers' => [ + 'Authorization' => 'Bearer '.$accessToken, + ], + ], $options)); + }); + } + public function getUsers(string $accessToken, int $limit = null, int $offset = null, array $options = []): array { return $this->get($this->urlGenerator->getUsersApiUrl(), $accessToken, $limit, $offset, $options); diff --git a/lib/php/auth-bundle/Repository/UserRepository.php b/lib/php/auth-bundle/Repository/UserRepository.php index fbe9c5854..a7e61a634 100644 --- a/lib/php/auth-bundle/Repository/UserRepository.php +++ b/lib/php/auth-bundle/Repository/UserRepository.php @@ -16,6 +16,13 @@ public function getUsers(int $limit = null, int $offset = null): array }); } + public function getUser(string $userId): ?array + { + return $this->keycloakRealmCache->get('users_'.$userId, function () use ($userId): array { + return $this->executeWithAccessToken(fn (string $accessToken): array => $this->oauthClient->getUser($accessToken, $userId)); + }); + } + public function getAclUsers(int $limit = null, int $offset = 0): array { return $this->getUsers($limit, $offset); diff --git a/lib/php/auth-bundle/Repository/UserRepositoryInterface.php b/lib/php/auth-bundle/Repository/UserRepositoryInterface.php index 07c363ee7..5a7be56b5 100644 --- a/lib/php/auth-bundle/Repository/UserRepositoryInterface.php +++ b/lib/php/auth-bundle/Repository/UserRepositoryInterface.php @@ -9,4 +9,5 @@ interface UserRepositoryInterface extends AclUserRepositoryInterface { public function getUsers(int $limit = null, int $offset = null): array; + public function getUser(string $userId): ?array; } diff --git a/uploader/api/composer.lock b/uploader/api/composer.lock index e82dade64..1aeb8aafb 100644 --- a/uploader/api/composer.lock +++ b/uploader/api/composer.lock @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/alchemy-fr/acl-bundle.git", - "reference": "c6f19b803b9afe749e5f90ece5b7e45751b5bd71" + "reference": "5a0316c05d67e0674674a87b1b007453b3d13ce8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alchemy-fr/acl-bundle/zipball/c6f19b803b9afe749e5f90ece5b7e45751b5bd71", - "reference": "c6f19b803b9afe749e5f90ece5b7e45751b5bd71", + "url": "https://api.github.com/repos/alchemy-fr/acl-bundle/zipball/5a0316c05d67e0674674a87b1b007453b3d13ce8", + "reference": "5a0316c05d67e0674674a87b1b007453b3d13ce8", "shasum": "" }, "require": { @@ -28,6 +28,7 @@ "symfony/event-dispatcher": "^4.0|^5.4|^6.1", "symfony/framework-bundle": "^4.0|^5.4|^6.1", "symfony/security-bundle": "^4.0|^5.4|^6.1", + "symfony/validator": "^6.3", "symfony/yaml": "^4.4|^5.4|^6.1" }, "require-dev": { @@ -55,7 +56,7 @@ "issues": "https://github.com/alchemy-fr/acl-bundle/issues", "source": "https://github.com/alchemy-fr/acl-bundle/tree/main" }, - "time": "2023-08-01T07:48:32+00:00" + "time": "2023-10-02T12:29:19+00:00" }, { "name": "alchemy/admin-bundle", diff --git a/uploader/client/package.json b/uploader/client/package.json index 092e89d22..6f3453ec1 100644 --- a/uploader/client/package.json +++ b/uploader/client/package.json @@ -15,7 +15,7 @@ "react-i18next": "^11.15.1", "react-loader-spinner": "^3.1.14", "@alchemy/auth": "link:./__lib/auth", - "react-ps": "link:./__lib/react-ps", + "@alchemy/react-ps": "link:./__lib/react-ps", "react-redux": "^5.1", "react-router-dom": "^5.0.0", "react-scripts": "^4.0.3", diff --git a/uploader/client/src/App.js b/uploader/client/src/App.js index d5b0a3838..aa2f909ae 100644 --- a/uploader/client/src/App.js +++ b/uploader/client/src/App.js @@ -15,7 +15,7 @@ import {withTranslation} from 'react-i18next'; import {keycloakClient, oauthClient, OAuthRedirect} from "./oauth"; import AuthError from "./components/page/AuthError"; import SelectTarget from "./components/page/SelectTarget"; -import {DashboardMenu} from "react-ps"; +import {DashboardMenu} from "@alchemy/react-ps"; import FullPageLoader from "./components/FullPageLoader"; import {authenticatedRequest} from "./lib/api"; diff --git a/uploader/client/src/oauth.js b/uploader/client/src/oauth.js index 4c6c74269..ac013ac96 100644 --- a/uploader/client/src/oauth.js +++ b/uploader/client/src/oauth.js @@ -50,7 +50,7 @@ export class OAuthRedirect extends PureComponent { }; componentDidMount() { - keycloakClient + oauthClient .getTokenFromAuthCode( this.getCode(), window.location.href.split('?')[0] diff --git a/uploader/client/yarn.lock b/uploader/client/yarn.lock index 8dad010dd..a9e33ff86 100644 --- a/uploader/client/yarn.lock +++ b/uploader/client/yarn.lock @@ -16,6 +16,10 @@ version "0.0.0" uid "" +"@alchemy/react-ps@link:./__lib/react-ps": + version "0.0.0" + uid "" + "@ampproject/remapping@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -9752,10 +9756,6 @@ react-overlays@^5.1.2: uncontrollable "^7.2.1" warning "^4.0.3" -"react-ps@link:./__lib/react-ps": - version "0.0.0" - uid "" - react-redux@^5.1: version "5.1.2" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57"