diff --git a/.gitattributes b/.gitattributes index b5a9e8e4..b1f2c5cf 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ -*.go linguist-detectable=true -*.js linguist-detectable=false -# Declare files that will always have LF line endings on checkout. -# Git will always convert line endings to LF on checkout. You should use this for files that must keep LF endings, even on Windows. +*.go linguist-detectable=true +*.js linguist-detectable=false +# Declare files that will always have LF line endings on checkout. +# Git will always convert line endings to LF on checkout. You should use this for files that must keep LF endings, even on Windows. *.sh text eol=lf \ No newline at end of file diff --git a/README.md b/README.md index a7f33583..b1397443 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,102 @@ -

📦⚡️ Casdoor

-

An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS

-

- - semantic-release - - - docker pull casbin/casdoor - - - GitHub Workflow Status (branch) - - - GitHub Release - - - Docker Image Version (latest semver) - -

- -

- - Go Report Card - - - license - - - GitHub issues - - - GitHub stars - - - GitHub forks - - - Crowdin - - - Discord - -

- -

- Sponsored by -
- - - - - - -
- Build auth with fraud prevention, faster.
Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.
-
-

- -## Online demo - -- Read-only site: https://door.casdoor.com (any modification operation will fail) -- Writable site: https://demo.casdoor.com (original data will be restored for every 5 minutes) - -## Documentation - -https://casdoor.org - -## Install - -- By source code: https://casdoor.org/docs/basic/server-installation -- By Docker: https://casdoor.org/docs/basic/try-with-docker -- By Kubernetes Helm: https://casdoor.org/docs/basic/try-with-helm - -## How to connect to Casdoor? - -https://casdoor.org/docs/how-to-connect/overview - -## Casdoor Public API - -- Docs: https://casdoor.org/docs/basic/public-api -- Swagger: https://door.casdoor.com/swagger - -## Integrations - -https://casdoor.org/docs/category/integrations - -## How to contact? - -- Discord: https://discord.gg/5rPsrAzK7S -- Contact: https://casdoor.org/help - -## Contribute - -For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community). - -### I18n translation - -If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the `web/` directory, please remember to add what you have added to the `web/src/locales/en/data.json` file. - -## License - -[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE) +

📦⚡️ Casdoor

+

An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS

+

+ + semantic-release + + + docker pull casbin/casdoor + + + GitHub Workflow Status (branch) + + + GitHub Release + + + Docker Image Version (latest semver) + +

+ +

+ + Go Report Card + + + license + + + GitHub issues + + + GitHub stars + + + GitHub forks + + + Crowdin + + + Discord + +

+ +

+ Sponsored by +
+ + + + + + +
+ Build auth with fraud prevention, faster.
Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.
+
+

+ +## Online demo + +- Read-only site: https://door.casdoor.com (any modification operation will fail) +- Writable site: https://demo.casdoor.com (original data will be restored for every 5 minutes) + +## Documentation + +https://casdoor.org + +## Install + +- By source code: https://casdoor.org/docs/basic/server-installation +- By Docker: https://casdoor.org/docs/basic/try-with-docker +- By Kubernetes Helm: https://casdoor.org/docs/basic/try-with-helm + +## How to connect to Casdoor? + +https://casdoor.org/docs/how-to-connect/overview + +## Casdoor Public API + +- Docs: https://casdoor.org/docs/basic/public-api +- Swagger: https://door.casdoor.com/swagger + +## Integrations + +https://casdoor.org/docs/category/integrations + +## How to contact? + +- Discord: https://discord.gg/5rPsrAzK7S +- Contact: https://casdoor.org/help + +## Contribute + +For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community). + +### I18n translation + +If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the `web/` directory, please remember to add what you have added to the `web/src/locales/en/data.json` file. + +## License + +[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE) diff --git a/web/craco.config.js b/web/craco.config.js index a2f55393..643a1385 100644 --- a/web/craco.config.js +++ b/web/craco.config.js @@ -1,97 +1,97 @@ -const CracoLessPlugin = require("craco-less"); -const path = require("path"); - -module.exports = { - devServer: { - proxy: { - "/api": { - target: "http://localhost:8000", - changeOrigin: true, - }, - "/swagger": { - target: "http://localhost:8000", - changeOrigin: true, - }, - "/files": { - target: "http://localhost:8000", - changeOrigin: true, - }, - "/.well-known/openid-configuration": { - target: "http://localhost:8000", - changeOrigin: true, - }, - "/cas/serviceValidate": { - target: "http://localhost:8000", - changeOrigin: true, - }, - "/cas/proxyValidate": { - target: "http://localhost:8000", - changeOrigin: true, - }, - "/cas/proxy": { - target: "http://localhost:8000", - changeOrigin: true, - }, - "/cas/validate": { - target: "http://localhost:8000", - changeOrigin: true, - }, - "/scim": { - target: "http://localhost:8000", - changeOrigin: true, - } - }, - }, - plugins: [ - { - plugin: CracoLessPlugin, - options: { - lessLoaderOptions: { - lessOptions: { - modifyVars: {"@primary-color": "rgb(89,54,213)", "@border-radius-base": "5px"}, - javascriptEnabled: true, - }, - }, - }, - }, - ], - webpack: { - configure: (webpackConfig, { env, paths }) => { - paths.appBuild = path.resolve(__dirname, "build-temp"); - webpackConfig.output.path = path.resolve(__dirname, "build-temp"); - - // ignore webpack warnings by source-map-loader - // https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546 - webpackConfig.ignoreWarnings = [ - function ignoreSourcemapsloaderWarnings(warning) { - return ( - warning.module && - warning.module.resource.includes("node_modules") && - warning.details && - warning.details.includes("source-map-loader") - ); - }, - ]; - - // use polyfill Buffer with Webpack 5 - // https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5 - // https://craco.js.org/docs/configuration/webpack/ - webpackConfig.resolve.fallback = { - buffer: require.resolve("buffer/"), - process: false, - util: false, - url: false, - zlib: false, - stream: false, - http: false, - https: false, - assert: false, - crypto: false, - os: false, - fs: false, - }; - - return webpackConfig; - }, - }, -}; +const CracoLessPlugin = require("craco-less"); +const path = require("path"); + +module.exports = { + devServer: { + proxy: { + "/api": { + target: "http://localhost:8000", + changeOrigin: true, + }, + "/swagger": { + target: "http://localhost:8000", + changeOrigin: true, + }, + "/files": { + target: "http://localhost:8000", + changeOrigin: true, + }, + "/.well-known/openid-configuration": { + target: "http://localhost:8000", + changeOrigin: true, + }, + "/cas/serviceValidate": { + target: "http://localhost:8000", + changeOrigin: true, + }, + "/cas/proxyValidate": { + target: "http://localhost:8000", + changeOrigin: true, + }, + "/cas/proxy": { + target: "http://localhost:8000", + changeOrigin: true, + }, + "/cas/validate": { + target: "http://localhost:8000", + changeOrigin: true, + }, + "/scim": { + target: "http://localhost:8000", + changeOrigin: true, + } + }, + }, + plugins: [ + { + plugin: CracoLessPlugin, + options: { + lessLoaderOptions: { + lessOptions: { + modifyVars: {"@primary-color": "rgb(89,54,213)", "@border-radius-base": "5px"}, + javascriptEnabled: true, + }, + }, + }, + }, + ], + webpack: { + configure: (webpackConfig, { env, paths }) => { + paths.appBuild = path.resolve(__dirname, "build-temp"); + webpackConfig.output.path = path.resolve(__dirname, "build-temp"); + + // ignore webpack warnings by source-map-loader + // https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546 + webpackConfig.ignoreWarnings = [ + function ignoreSourcemapsloaderWarnings(warning) { + return ( + warning.module && + warning.module.resource.includes("node_modules") && + warning.details && + warning.details.includes("source-map-loader") + ); + }, + ]; + + // use polyfill Buffer with Webpack 5 + // https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5 + // https://craco.js.org/docs/configuration/webpack/ + webpackConfig.resolve.fallback = { + buffer: require.resolve("buffer/"), + process: false, + util: false, + url: false, + zlib: false, + stream: false, + http: false, + https: false, + assert: false, + crypto: false, + os: false, + fs: false, + }; + + return webpackConfig; + }, + }, +}; diff --git a/web/mv.js b/web/mv.js index dbbfd7cf..9f78ab43 100644 --- a/web/mv.js +++ b/web/mv.js @@ -1,21 +1,21 @@ -const fs = require("fs"); -const path = require("path"); - -const sourceDir = path.join(__dirname, "build-temp"); -const targetDir = path.join(__dirname, "build"); - -if (!fs.existsSync(sourceDir)) { - // eslint-disable-next-line no-console - console.error(`Source directory "${sourceDir}" does not exist.`); - process.exit(1); -} - -if (fs.existsSync(targetDir)) { - fs.rmSync(targetDir, {recursive: true, force: true}); - // eslint-disable-next-line no-console - console.log(`Target directory "${targetDir}" has been deleted successfully.`); -} - -fs.renameSync(sourceDir, targetDir); -// eslint-disable-next-line no-console -console.log(`Renamed "${sourceDir}" to "${targetDir}" successfully.`); +const fs = require("fs"); +const path = require("path"); + +const sourceDir = path.join(__dirname, "build-temp"); +const targetDir = path.join(__dirname, "build"); + +if (!fs.existsSync(sourceDir)) { + // eslint-disable-next-line no-console + console.error(`Source directory "${sourceDir}" does not exist.`); + process.exit(1); +} + +if (fs.existsSync(targetDir)) { + fs.rmSync(targetDir, {recursive: true, force: true}); + // eslint-disable-next-line no-console + console.log(`Target directory "${targetDir}" has been deleted successfully.`); +} + +fs.renameSync(sourceDir, targetDir); +// eslint-disable-next-line no-console +console.log(`Renamed "${sourceDir}" to "${targetDir}" successfully.`); diff --git a/web/src/Conf.js b/web/src/Conf.js index df9cab56..2148c606 100644 --- a/web/src/Conf.js +++ b/web/src/Conf.js @@ -1,36 +1,36 @@ -// Copyright 2021 The Casdoor Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export const DefaultApplication = "app-built-in"; - -export const CasvisorUrl = ""; - -export const ShowGithubCorner = false; -export const IsDemoMode = false; - -export const ForceLanguage = ""; -export const DefaultLanguage = "en"; - -export const InitThemeAlgorithm = true; -export const ThemeDefault = { - themeType: "default", - colorPrimary: "#5734d3", - borderRadius: 6, - isCompact: false, -}; - -export const CustomFooter = null; - -// Blank or null to hide Ai Assistant button -export const AiAssistantUrl = "https://ai.casbin.com"; +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export const DefaultApplication = "app-built-in"; + +export const CasvisorUrl = ""; + +export const ShowGithubCorner = false; +export const IsDemoMode = false; + +export const ForceLanguage = ""; +export const DefaultLanguage = "en"; + +export const InitThemeAlgorithm = true; +export const ThemeDefault = { + themeType: "default", + colorPrimary: "#5734d3", + borderRadius: 6, + isCompact: false, +}; + +export const CustomFooter = null; + +// Blank or null to hide Ai Assistant button +export const AiAssistantUrl = "https://ai.casbin.com"; diff --git a/web/src/ResourceListPage.js b/web/src/ResourceListPage.js index f8a8fb91..42dceca2 100644 --- a/web/src/ResourceListPage.js +++ b/web/src/ResourceListPage.js @@ -1,341 +1,341 @@ -// Copyright 2021 The Casdoor Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import React from "react"; -import {Button, Image, Table, Upload} from "antd"; -import {UploadOutlined} from "@ant-design/icons"; -import copy from "copy-to-clipboard"; -import * as Setting from "./Setting"; -import * as ResourceBackend from "./backend/ResourceBackend"; -import i18next from "i18next"; -import {Link} from "react-router-dom"; -import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./common/modal/PopconfirmModal"; - -class ResourceListPage extends BaseListPage { - constructor(props) { - super(props); - } - - componentDidMount() { - this.setState({ - fileList: [], - uploading: false, - }); - } - - deleteResource(i) { - ResourceBackend.deleteResource(this.state.data[i]) - .then((res) => { - if (res.status === "ok") { - Setting.showMessage("success", i18next.t("general:Successfully deleted")); - this.fetch({ - pagination: { - ...this.state.pagination, - current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current, - }, - }); - } else { - Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`); - } - }) - .catch(error => { - Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`); - }); - } - - handleUpload(info) { - this.setState({uploading: true}); - const filename = info.fileList[0].name; - const fullFilePath = `resource/${this.props.account.owner}/${this.props.account.name}/${filename}`; - ResourceBackend.uploadResource(this.props.account.owner, this.props.account.name, "custom", "ResourceListPage", fullFilePath, info.file) - .then(res => { - if (res.status === "ok") { - Setting.showMessage("success", i18next.t("application:File uploaded successfully")); - - const {pagination} = this.state; - this.fetch({pagination}); - } else { - Setting.showMessage("error", res.msg); - } - }).finally(() => { - this.setState({uploading: false}); - }); - } - - renderUpload() { - return ( - {return false;}} onChange={info => {this.handleUpload(info);}}> - - - ); - } - - renderTable(resources) { - const columns = [ - { - title: i18next.t("general:Provider"), - dataIndex: "provider", - key: "provider", - width: "150px", - sorter: true, - ...this.getColumnSearchProps("provider"), - render: (text, record, index) => { - return ( - - {text} - - ); - }, - }, - { - title: i18next.t("general:Organization"), - dataIndex: "owner", - key: "owner", - width: "120px", - sorter: true, - ...this.getColumnSearchProps("owner"), - render: (text, record, index) => { - return ( - - {text} - - ); - }, - }, - { - title: i18next.t("general:Application"), - dataIndex: "application", - key: "application", - width: "80px", - sorter: true, - ...this.getColumnSearchProps("application"), - render: (text, record, index) => { - return ( - - {text} - - ); - }, - }, - { - title: i18next.t("general:User"), - dataIndex: "user", - key: "user", - width: "80px", - sorter: true, - ...this.getColumnSearchProps("user"), - render: (text, record, index) => { - return ( - - {text} - - ); - }, - }, - { - title: i18next.t("resource:Parent"), - dataIndex: "parent", - key: "parent", - width: "80px", - sorter: true, - ...this.getColumnSearchProps("parent"), - }, - { - title: i18next.t("general:Name"), - dataIndex: "name", - key: "name", - width: "150px", - sorter: true, - ...this.getColumnSearchProps("name"), - }, - { - title: i18next.t("general:Created time"), - dataIndex: "createdTime", - key: "createdTime", - width: "150px", - sorter: true, - render: (text, record, index) => { - return Setting.getFormattedDate(text); - }, - }, - { - title: i18next.t("user:Tag"), - dataIndex: "tag", - key: "tag", - width: "80px", - sorter: true, - ...this.getColumnSearchProps("tag"), - }, - // { - // title: i18next.t("resource:File name"), - // dataIndex: 'fileName', - // key: 'fileName', - // width: '120px', - // sorter: (a, b) => a.fileName.localeCompare(b.fileName), - // }, - { - title: i18next.t("provider:Type"), - dataIndex: "fileType", - key: "fileType", - width: "80px", - sorter: true, - ...this.getColumnSearchProps("fileType"), - }, - { - title: i18next.t("resource:Format"), - dataIndex: "fileFormat", - key: "fileFormat", - width: "80px", - sorter: true, - ...this.getColumnSearchProps("fileFormat"), - }, - { - title: i18next.t("resource:File size"), - dataIndex: "fileSize", - key: "fileSize", - width: "100px", - sorter: true, - render: (text, record, index) => { - return Setting.getFriendlyFileSize(text); - }, - }, - { - title: i18next.t("general:Preview"), - dataIndex: "preview", - key: "preview", - width: "100px", - fixed: (Setting.isMobile()) ? "false" : "right", - render: (text, record, index) => { - if (record.fileType === "image") { - const errorImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=="; - return ( - - ); - } else if (record.fileType === "video") { - return ( - - ); - } - }, - }, - { - title: i18next.t("general:URL"), - dataIndex: "url", - key: "url", - width: "120px", - fixed: (Setting.isMobile()) ? "false" : "right", - render: (text, record, index) => { - return ( -
- -
- ); - }, - }, - { - title: i18next.t("general:Action"), - dataIndex: "", - key: "op", - width: "70px", - fixed: (Setting.isMobile()) ? "false" : "right", - render: (text, record, index) => { - return ( -
- this.deleteResource(index)} - okText={i18next.t("general:OK")} - cancelText={i18next.t("general:Cancel")} - > - -
- ); - }, - }, - ]; - - const paginationProps = { - total: this.state.pagination.total, - showQuickJumper: true, - showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), - }; - - return ( -
- ( -
- {i18next.t("general:Resources")}     - {/* */} - { - this.renderUpload() - } -
- )} - loading={this.state.loading} - onChange={this.handleTableChange} - /> - - ); - } - - fetch = (params = {}) => { - const field = params.searchedColumn, value = params.searchText; - const sortField = params.sortField, sortOrder = params.sortOrder; - this.setState({loading: true}); - ResourceBackend.getResources(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) - .then((res) => { - this.setState({ - loading: false, - }); - if (res.status === "ok") { - this.setState({ - data: res.data, - pagination: { - ...params.pagination, - total: res.data2, - }, - searchText: params.searchText, - searchedColumn: params.searchedColumn, - }); - } else { - if (res.data.includes("Please login first")) { - this.setState({ - loading: false, - isAuthorized: false, - }); - } - } - }); - }; -} - -export default ResourceListPage; +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from "react"; +import {Button, Image, Table, Upload} from "antd"; +import {UploadOutlined} from "@ant-design/icons"; +import copy from "copy-to-clipboard"; +import * as Setting from "./Setting"; +import * as ResourceBackend from "./backend/ResourceBackend"; +import i18next from "i18next"; +import {Link} from "react-router-dom"; +import BaseListPage from "./BaseListPage"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; + +class ResourceListPage extends BaseListPage { + constructor(props) { + super(props); + } + + componentDidMount() { + this.setState({ + fileList: [], + uploading: false, + }); + } + + deleteResource(i) { + ResourceBackend.deleteResource(this.state.data[i]) + .then((res) => { + if (res.status === "ok") { + Setting.showMessage("success", i18next.t("general:Successfully deleted")); + this.fetch({ + pagination: { + ...this.state.pagination, + current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current, + }, + }); + } else { + Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`); + } + }) + .catch(error => { + Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`); + }); + } + + handleUpload(info) { + this.setState({uploading: true}); + const filename = info.fileList[0].name; + const fullFilePath = `resource/${this.props.account.owner}/${this.props.account.name}/${filename}`; + ResourceBackend.uploadResource(this.props.account.owner, this.props.account.name, "custom", "ResourceListPage", fullFilePath, info.file) + .then(res => { + if (res.status === "ok") { + Setting.showMessage("success", i18next.t("application:File uploaded successfully")); + + const {pagination} = this.state; + this.fetch({pagination}); + } else { + Setting.showMessage("error", res.msg); + } + }).finally(() => { + this.setState({uploading: false}); + }); + } + + renderUpload() { + return ( + {return false;}} onChange={info => {this.handleUpload(info);}}> + + + ); + } + + renderTable(resources) { + const columns = [ + { + title: i18next.t("general:Provider"), + dataIndex: "provider", + key: "provider", + width: "150px", + sorter: true, + ...this.getColumnSearchProps("provider"), + render: (text, record, index) => { + return ( + + {text} + + ); + }, + }, + { + title: i18next.t("general:Organization"), + dataIndex: "owner", + key: "owner", + width: "120px", + sorter: true, + ...this.getColumnSearchProps("owner"), + render: (text, record, index) => { + return ( + + {text} + + ); + }, + }, + { + title: i18next.t("general:Application"), + dataIndex: "application", + key: "application", + width: "80px", + sorter: true, + ...this.getColumnSearchProps("application"), + render: (text, record, index) => { + return ( + + {text} + + ); + }, + }, + { + title: i18next.t("general:User"), + dataIndex: "user", + key: "user", + width: "80px", + sorter: true, + ...this.getColumnSearchProps("user"), + render: (text, record, index) => { + return ( + + {text} + + ); + }, + }, + { + title: i18next.t("resource:Parent"), + dataIndex: "parent", + key: "parent", + width: "80px", + sorter: true, + ...this.getColumnSearchProps("parent"), + }, + { + title: i18next.t("general:Name"), + dataIndex: "name", + key: "name", + width: "150px", + sorter: true, + ...this.getColumnSearchProps("name"), + }, + { + title: i18next.t("general:Created time"), + dataIndex: "createdTime", + key: "createdTime", + width: "150px", + sorter: true, + render: (text, record, index) => { + return Setting.getFormattedDate(text); + }, + }, + { + title: i18next.t("user:Tag"), + dataIndex: "tag", + key: "tag", + width: "80px", + sorter: true, + ...this.getColumnSearchProps("tag"), + }, + // { + // title: i18next.t("resource:File name"), + // dataIndex: 'fileName', + // key: 'fileName', + // width: '120px', + // sorter: (a, b) => a.fileName.localeCompare(b.fileName), + // }, + { + title: i18next.t("provider:Type"), + dataIndex: "fileType", + key: "fileType", + width: "80px", + sorter: true, + ...this.getColumnSearchProps("fileType"), + }, + { + title: i18next.t("resource:Format"), + dataIndex: "fileFormat", + key: "fileFormat", + width: "80px", + sorter: true, + ...this.getColumnSearchProps("fileFormat"), + }, + { + title: i18next.t("resource:File size"), + dataIndex: "fileSize", + key: "fileSize", + width: "100px", + sorter: true, + render: (text, record, index) => { + return Setting.getFriendlyFileSize(text); + }, + }, + { + title: i18next.t("general:Preview"), + dataIndex: "preview", + key: "preview", + width: "100px", + fixed: (Setting.isMobile()) ? "false" : "right", + render: (text, record, index) => { + if (record.fileType === "image") { + const errorImage = "data:image/png;base64,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"; + return ( + + ); + } else if (record.fileType === "video") { + return ( + + ); + } + }, + }, + { + title: i18next.t("general:URL"), + dataIndex: "url", + key: "url", + width: "120px", + fixed: (Setting.isMobile()) ? "false" : "right", + render: (text, record, index) => { + return ( +
+ +
+ ); + }, + }, + { + title: i18next.t("general:Action"), + dataIndex: "", + key: "op", + width: "70px", + fixed: (Setting.isMobile()) ? "false" : "right", + render: (text, record, index) => { + return ( +
+ this.deleteResource(index)} + okText={i18next.t("general:OK")} + cancelText={i18next.t("general:Cancel")} + > + +
+ ); + }, + }, + ]; + + const paginationProps = { + total: this.state.pagination.total, + showQuickJumper: true, + showSizeChanger: true, + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), + }; + + return ( +
+
( +
+ {i18next.t("general:Resources")}     + {/* */} + { + this.renderUpload() + } +
+ )} + loading={this.state.loading} + onChange={this.handleTableChange} + /> + + ); + } + + fetch = (params = {}) => { + const field = params.searchedColumn, value = params.searchText; + const sortField = params.sortField, sortOrder = params.sortOrder; + this.setState({loading: true}); + ResourceBackend.getResources(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + this.setState({ + loading: false, + }); + if (res.status === "ok") { + this.setState({ + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); + } else { + if (res.data.includes("Please login first")) { + this.setState({ + loading: false, + isAuthorized: false, + }); + } + } + }); + }; +} + +export default ResourceListPage; diff --git a/web/src/auth/OidcDiscoveryPage.js b/web/src/auth/OidcDiscoveryPage.js index 7680179f..8a0793c4 100644 --- a/web/src/auth/OidcDiscoveryPage.js +++ b/web/src/auth/OidcDiscoveryPage.js @@ -1,30 +1,30 @@ -// Copyright 2021 The Casdoor Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import React from "react"; -import * as Setting from "../Setting"; - -class OdicDiscoveryPage extends React.Component { - UNSAFE_componentWillMount() { - if (Setting.isLocalhost()) { - Setting.goToLink(`${Setting.ServerUrl}/.well-known/openid-configuration`); - } - } - - render() { - return null; - } -} - -export default OdicDiscoveryPage; +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from "react"; +import * as Setting from "../Setting"; + +class OdicDiscoveryPage extends React.Component { + UNSAFE_componentWillMount() { + if (Setting.isLocalhost()) { + Setting.goToLink(`${Setting.ServerUrl}/.well-known/openid-configuration`); + } + } + + render() { + return null; + } +} + +export default OdicDiscoveryPage; diff --git a/web/src/auth/QqLoginButton.js b/web/src/auth/QqLoginButton.js index 34092f75..59da252e 100644 --- a/web/src/auth/QqLoginButton.js +++ b/web/src/auth/QqLoginButton.js @@ -1,31 +1,31 @@ -// Copyright 2021 The Casdoor Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {createButton} from "react-social-login-buttons"; - -function Icon({width = 24, height = 24, color}) { - return ; -} - -const config = { - text: "Sign in with QQ", - icon: Icon, - iconFormat: name => `fa fa-${name}`, - style: {background: "rgb(94,188,249)"}, - activeStyle: {background: "rgb(76,143,208)"}, -}; - -const QqLoginButton = createButton(config); - -export default QqLoginButton; +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {createButton} from "react-social-login-buttons"; + +function Icon({width = 24, height = 24, color}) { + return ; +} + +const config = { + text: "Sign in with QQ", + icon: Icon, + iconFormat: name => `fa fa-${name}`, + style: {background: "rgb(94,188,249)"}, + activeStyle: {background: "rgb(76,143,208)"}, +}; + +const QqLoginButton = createButton(config); + +export default QqLoginButton; diff --git a/web/src/backend/ResourceBackend.js b/web/src/backend/ResourceBackend.js index 844e9c18..4afe37d2 100644 --- a/web/src/backend/ResourceBackend.js +++ b/web/src/backend/ResourceBackend.js @@ -1,85 +1,85 @@ -// Copyright 2021 The Casdoor Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as Setting from "../Setting"; - -export function getResources(owner, user, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { - return fetch(`${Setting.ServerUrl}/api/get-resources?owner=${owner}&user=${user}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { - method: "GET", - credentials: "include", - headers: { - "Accept-Language": Setting.getAcceptLanguage(), - }, - }).then(res => res.json()); -} - -export function getResource(owner, name) { - return fetch(`${Setting.ServerUrl}/api/get-resource?id=${owner}/${encodeURIComponent(name)}`, { - method: "GET", - credentials: "include", - headers: { - "Accept-Language": Setting.getAcceptLanguage(), - }, - }).then(res => res.json()); -} - -export function updateResource(owner, name, resource) { - const newResource = Setting.deepCopy(resource); - return fetch(`${Setting.ServerUrl}/api/update-resource?id=${owner}/${encodeURIComponent(name)}`, { - method: "POST", - credentials: "include", - body: JSON.stringify(newResource), - headers: { - "Accept-Language": Setting.getAcceptLanguage(), - }, - }).then(res => res.json()); -} - -export function addResource(resource) { - const newResource = Setting.deepCopy(resource); - return fetch(`${Setting.ServerUrl}/api/add-resource`, { - method: "POST", - credentials: "include", - body: JSON.stringify(newResource), - headers: { - "Accept-Language": Setting.getAcceptLanguage(), - }, - }).then(res => res.json()); -} - -export function deleteResource(resource, provider = "") { - const newResource = Setting.deepCopy(resource); - return fetch(`${Setting.ServerUrl}/api/delete-resource?provider=${provider}`, { - method: "POST", - credentials: "include", - body: JSON.stringify(newResource), - headers: { - "Accept-Language": Setting.getAcceptLanguage(), - }, - }).then(res => res.json()); -} - -export function uploadResource(owner, user, tag, parent, fullFilePath, file, provider = "") { - const application = "app-built-in"; - const formData = new FormData(); - formData.append("file", file); - return fetch(`${Setting.ServerUrl}/api/upload-resource?owner=${owner}&user=${user}&application=${application}&tag=${tag}&parent=${parent}&fullFilePath=${encodeURIComponent(fullFilePath)}&provider=${provider}`, { - body: formData, - method: "POST", - credentials: "include", - headers: { - "Accept-Language": Setting.getAcceptLanguage(), - }, - }).then(res => res.json()); -} +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as Setting from "../Setting"; + +export function getResources(owner, user, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-resources?owner=${owner}&user=${user}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { + method: "GET", + credentials: "include", + headers: { + "Accept-Language": Setting.getAcceptLanguage(), + }, + }).then(res => res.json()); +} + +export function getResource(owner, name) { + return fetch(`${Setting.ServerUrl}/api/get-resource?id=${owner}/${encodeURIComponent(name)}`, { + method: "GET", + credentials: "include", + headers: { + "Accept-Language": Setting.getAcceptLanguage(), + }, + }).then(res => res.json()); +} + +export function updateResource(owner, name, resource) { + const newResource = Setting.deepCopy(resource); + return fetch(`${Setting.ServerUrl}/api/update-resource?id=${owner}/${encodeURIComponent(name)}`, { + method: "POST", + credentials: "include", + body: JSON.stringify(newResource), + headers: { + "Accept-Language": Setting.getAcceptLanguage(), + }, + }).then(res => res.json()); +} + +export function addResource(resource) { + const newResource = Setting.deepCopy(resource); + return fetch(`${Setting.ServerUrl}/api/add-resource`, { + method: "POST", + credentials: "include", + body: JSON.stringify(newResource), + headers: { + "Accept-Language": Setting.getAcceptLanguage(), + }, + }).then(res => res.json()); +} + +export function deleteResource(resource, provider = "") { + const newResource = Setting.deepCopy(resource); + return fetch(`${Setting.ServerUrl}/api/delete-resource?provider=${provider}`, { + method: "POST", + credentials: "include", + body: JSON.stringify(newResource), + headers: { + "Accept-Language": Setting.getAcceptLanguage(), + }, + }).then(res => res.json()); +} + +export function uploadResource(owner, user, tag, parent, fullFilePath, file, provider = "") { + const application = "app-built-in"; + const formData = new FormData(); + formData.append("file", file); + return fetch(`${Setting.ServerUrl}/api/upload-resource?owner=${owner}&user=${user}&application=${application}&tag=${tag}&parent=${parent}&fullFilePath=${encodeURIComponent(fullFilePath)}&provider=${provider}`, { + body: formData, + method: "POST", + credentials: "include", + headers: { + "Accept-Language": Setting.getAcceptLanguage(), + }, + }).then(res => res.json()); +} diff --git a/web/src/common/OAuthWidget.js b/web/src/common/OAuthWidget.js index a0f06d2b..84fed699 100644 --- a/web/src/common/OAuthWidget.js +++ b/web/src/common/OAuthWidget.js @@ -1,229 +1,229 @@ -// Copyright 2021 The Casdoor Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import React from "react"; -import {Button, Col, Row} from "antd"; -import i18next from "i18next"; -import * as UserBackend from "../backend/UserBackend"; -import * as Setting from "../Setting"; -import * as Provider from "../auth/Provider"; -import * as AuthBackend from "../auth/AuthBackend"; -import {goToWeb3Url} from "../auth/ProviderButton"; -import AccountAvatar from "../account/AccountAvatar"; -import {WechatOfficialAccountModal} from "../auth/Util"; - -class OAuthWidget extends React.Component { - constructor(props) { - super(props); - this.state = { - classes: props, - addressOptions: [], - affiliationOptions: [], - }; - } - - UNSAFE_componentWillMount() { - this.getAddressOptions(this.props.application); - this.getAffiliationOptions(this.props.application, this.props.user); - } - - getAddressOptions(application) { - if (application.affiliationUrl === "") { - return; - } - - const addressUrl = application.affiliationUrl.split("|")[0]; - UserBackend.getAddressOptions(addressUrl) - .then((addressOptions) => { - this.setState({ - addressOptions: addressOptions, - }); - }); - } - - getAffiliationOptions(application, user) { - if (application.affiliationUrl === "") { - return; - } - - const affiliationUrl = application.affiliationUrl.split("|")[1]; - const code = user.address[user.address.length - 1]; - UserBackend.getAffiliationOptions(affiliationUrl, code) - .then((affiliationOptions) => { - this.setState({ - affiliationOptions: affiliationOptions, - }); - }); - } - - updateUserField(key, value) { - this.props.onUpdateUserField(key, value); - } - - unlinked() { - this.props.onUnlinked(); - } - - getProviderLink(user, provider) { - if (provider.type === "GitHub") { - return `https://github.com/${this.getUserProperty(user, provider.type, "username")}`; - } else if (provider.type === "Google") { - return "https://mail.google.com"; - } else { - return ""; - } - } - - getUserProperty(user, providerType, propertyName) { - const key = `oauth_${providerType}_${propertyName}`; - if (user.properties === null) {return "";} - return user.properties[key]; - } - - unlinkUser(providerType, linkedValue) { - const body = { - providerType: providerType, - // should add the unlink user's info, cause the user may not be logged in, but a admin want to unlink the user. - user: this.props.user, - }; - if (providerType === "MetaMask" || providerType === "Web3Onboard") { - import("../auth/Web3Auth") - .then(module => { - const delWeb3AuthToken = module.delWeb3AuthToken; - delWeb3AuthToken(linkedValue); - AuthBackend.unlink(body) - .then((res) => { - if (res.status === "ok") { - Setting.showMessage("success", "Unlinked successfully"); - - this.unlinked(); - } else { - Setting.showMessage("error", `Failed to unlink: ${res.msg}`); - } - }); - }); - return; - } - AuthBackend.unlink(body) - .then((res) => { - if (res.status === "ok") { - Setting.showMessage("success", "Unlinked successfully"); - - this.unlinked(); - } else { - Setting.showMessage("error", `Failed to unlink: ${res.msg}`); - } - }); - } - - renderIdp(user, application, providerItem) { - const provider = providerItem.provider; - const linkedValue = user[provider.type.toLowerCase()]; - const profileUrl = this.getProviderLink(user, provider); - const id = this.getUserProperty(user, provider.type, "id"); - const username = this.getUserProperty(user, provider.type, "username"); - const displayName = this.getUserProperty(user, provider.type, "displayName"); - const email = this.getUserProperty(user, provider.type, "email"); - let avatarUrl = this.getUserProperty(user, provider.type, "avatarUrl"); - // the account user - const account = this.props.account; - - if (avatarUrl === "" || avatarUrl === undefined) { - avatarUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAQAAACROWYpAAAAHElEQVR42mNkoAAwjmoe1TyqeVTzqOZRzcNZMwB18wAfEFQkPQAAAABJRU5ErkJggg=="; - } - - let name = (username === undefined) ? displayName : `${displayName} (${username})`; - if (name === undefined) { - if (id !== undefined) { - name = id; - } else if (email !== undefined) { - name = email; - } else { - name = linkedValue; - } - } - - let linkButtonWidth = "110px"; - if (Setting.getLanguage() === "id") { - linkButtonWidth = "160px"; - } - - return ( - - - { - Setting.getProviderLogo(provider) - } - - { - `${provider.type}:` - } - - - - - - { - linkedValue === "" ? ( - `(${i18next.t("general:empty")})` - ) : ( - profileUrl === "" ? name : ( - - { - name - } - - ) - ) - } - - { - linkedValue === "" ? ( - provider.category === "Web3" ? ( - - ) : ( - provider.type === "WeChat" && provider.clientId2 !== "" && provider.clientSecret2 !== "" && provider.disableSsl === true && !navigator.userAgent.includes("MicroMessenger") ? ( - - - - ) : ( - - - - ) - ) - ) : ( - - ) - } - - - ); - } - - render() { - return this.renderIdp(this.props.user, this.props.application, this.props.providerItem); - } -} - -export default OAuthWidget; +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from "react"; +import {Button, Col, Row} from "antd"; +import i18next from "i18next"; +import * as UserBackend from "../backend/UserBackend"; +import * as Setting from "../Setting"; +import * as Provider from "../auth/Provider"; +import * as AuthBackend from "../auth/AuthBackend"; +import {goToWeb3Url} from "../auth/ProviderButton"; +import AccountAvatar from "../account/AccountAvatar"; +import {WechatOfficialAccountModal} from "../auth/Util"; + +class OAuthWidget extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + addressOptions: [], + affiliationOptions: [], + }; + } + + UNSAFE_componentWillMount() { + this.getAddressOptions(this.props.application); + this.getAffiliationOptions(this.props.application, this.props.user); + } + + getAddressOptions(application) { + if (application.affiliationUrl === "") { + return; + } + + const addressUrl = application.affiliationUrl.split("|")[0]; + UserBackend.getAddressOptions(addressUrl) + .then((addressOptions) => { + this.setState({ + addressOptions: addressOptions, + }); + }); + } + + getAffiliationOptions(application, user) { + if (application.affiliationUrl === "") { + return; + } + + const affiliationUrl = application.affiliationUrl.split("|")[1]; + const code = user.address[user.address.length - 1]; + UserBackend.getAffiliationOptions(affiliationUrl, code) + .then((affiliationOptions) => { + this.setState({ + affiliationOptions: affiliationOptions, + }); + }); + } + + updateUserField(key, value) { + this.props.onUpdateUserField(key, value); + } + + unlinked() { + this.props.onUnlinked(); + } + + getProviderLink(user, provider) { + if (provider.type === "GitHub") { + return `https://github.com/${this.getUserProperty(user, provider.type, "username")}`; + } else if (provider.type === "Google") { + return "https://mail.google.com"; + } else { + return ""; + } + } + + getUserProperty(user, providerType, propertyName) { + const key = `oauth_${providerType}_${propertyName}`; + if (user.properties === null) {return "";} + return user.properties[key]; + } + + unlinkUser(providerType, linkedValue) { + const body = { + providerType: providerType, + // should add the unlink user's info, cause the user may not be logged in, but a admin want to unlink the user. + user: this.props.user, + }; + if (providerType === "MetaMask" || providerType === "Web3Onboard") { + import("../auth/Web3Auth") + .then(module => { + const delWeb3AuthToken = module.delWeb3AuthToken; + delWeb3AuthToken(linkedValue); + AuthBackend.unlink(body) + .then((res) => { + if (res.status === "ok") { + Setting.showMessage("success", "Unlinked successfully"); + + this.unlinked(); + } else { + Setting.showMessage("error", `Failed to unlink: ${res.msg}`); + } + }); + }); + return; + } + AuthBackend.unlink(body) + .then((res) => { + if (res.status === "ok") { + Setting.showMessage("success", "Unlinked successfully"); + + this.unlinked(); + } else { + Setting.showMessage("error", `Failed to unlink: ${res.msg}`); + } + }); + } + + renderIdp(user, application, providerItem) { + const provider = providerItem.provider; + const linkedValue = user[provider.type.toLowerCase()]; + const profileUrl = this.getProviderLink(user, provider); + const id = this.getUserProperty(user, provider.type, "id"); + const username = this.getUserProperty(user, provider.type, "username"); + const displayName = this.getUserProperty(user, provider.type, "displayName"); + const email = this.getUserProperty(user, provider.type, "email"); + let avatarUrl = this.getUserProperty(user, provider.type, "avatarUrl"); + // the account user + const account = this.props.account; + + if (avatarUrl === "" || avatarUrl === undefined) { + avatarUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAQAAACROWYpAAAAHElEQVR42mNkoAAwjmoe1TyqeVTzqOZRzcNZMwB18wAfEFQkPQAAAABJRU5ErkJggg=="; + } + + let name = (username === undefined) ? displayName : `${displayName} (${username})`; + if (name === undefined) { + if (id !== undefined) { + name = id; + } else if (email !== undefined) { + name = email; + } else { + name = linkedValue; + } + } + + let linkButtonWidth = "110px"; + if (Setting.getLanguage() === "id") { + linkButtonWidth = "160px"; + } + + return ( + + + { + Setting.getProviderLogo(provider) + } + + { + `${provider.type}:` + } + + + + + + { + linkedValue === "" ? ( + `(${i18next.t("general:empty")})` + ) : ( + profileUrl === "" ? name : ( + + { + name + } + + ) + ) + } + + { + linkedValue === "" ? ( + provider.category === "Web3" ? ( + + ) : ( + provider.type === "WeChat" && provider.clientId2 !== "" && provider.clientSecret2 !== "" && provider.disableSsl === true && !navigator.userAgent.includes("MicroMessenger") ? ( + + + + ) : ( + + + + ) + ) + ) : ( + + ) + } + + + ); + } + + render() { + return this.renderIdp(this.props.user, this.props.application, this.props.providerItem); + } +} + +export default OAuthWidget;