Compare commits

..

6 Commits

Author SHA1 Message Date
7e756b8ee2 feat: manager applications in organization scope (#1290)
* feat: manager applications in organization scope(front end)

* fix: application can use own organization and admin provider

* fix: improve methed to get provider

* fix: modify provider methods by convention
2022-11-21 01:17:55 +08:00
19ba37e0c2 feat: can specify available UI languages for an organization (#1306) 2022-11-19 22:11:19 +08:00
b98ce19211 feat: fix bug in GetDefaultApplication() that caused login error for other orgs (#1299)
* fix:fix bug in GetDefaultApplication

* fix:fix bug in GetDefaultApplication
2022-11-16 00:39:05 +08:00
37d1a73c0c feat: encode redirectUri (#1297) 2022-11-15 19:05:59 +08:00
727877cf54 fix: illegal user when new a permission (#1298) 2022-11-15 14:19:20 +08:00
939b416717 fix: limit only under PC can login by following Wechat Official Account (#1293) 2022-11-14 09:03:55 +08:00
38 changed files with 230 additions and 103 deletions

View File

@ -46,7 +46,7 @@ func (c *ApiController) GetApplications() {
if organization == "" {
applications = object.GetApplications(owner)
} else {
applications = object.GetApplicationsByOrganizationName(owner, organization)
applications = object.GetOrganizationApplications(owner, organization)
}
c.Data["json"] = object.GetMaskedApplications(applications, userId)
@ -103,17 +103,31 @@ func (c *ApiController) GetUserApplication() {
// @router /get-organization-applications [get]
func (c *ApiController) GetOrganizationApplications() {
userId := c.GetSessionUsername()
owner := c.Input().Get("owner")
organization := c.Input().Get("organization")
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if organization == "" {
c.ResponseError(c.T("ParameterErr.OrgMissingErr"))
return
}
applications := object.GetApplicationsByOrganizationName(owner, organization)
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
if limit == "" || page == "" {
var applications []*object.Application
applications = object.GetOrganizationApplications(owner, organization)
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationApplicationCount(owner, organization, field, value)))
applications := object.GetMaskedApplications(object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
c.ResponseOk(applications, paginator.Nums())
}
}
// UpdateApplication

View File

@ -307,7 +307,7 @@ func (c *ApiController) Login() {
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
provider := object.GetProvider(util.GetId(form.Provider))
providerItem := application.GetProviderItem(provider.Name)
if !providerItem.IsProviderVisible() {
c.ResponseError(fmt.Sprintf(c.T("ProviderErr.ProviderNotEnabled"), provider.Name))

View File

@ -81,7 +81,6 @@ func (c *ApiController) GetGlobalProviders() {
// @router /get-provider [get]
func (c *ApiController) GetProvider() {
id := c.Input().Get("id")
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id))
c.ServeJSON()
}

View File

@ -60,7 +60,7 @@ func (c *ApiController) SendEmail() {
var provider *object.Provider
if emailForm.Provider != "" {
// called by frontend's TestEmailWidget, provider name is set by frontend
provider = object.GetProvider(fmt.Sprintf("admin/%s", emailForm.Provider))
provider = object.GetProvider(util.GetId(emailForm.Provider))
} else {
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
var ok bool

View File

@ -21,9 +21,10 @@ import (
"testing"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func TestDeployStaticFiles(t *testing.T) {
provider := object.GetProvider("admin/provider_storage_aliyun_oss")
provider := object.GetProvider(util.GetId("provider_storage_aliyun_oss"))
deployStaticFiles(provider)
}

View File

@ -85,6 +85,16 @@ func GetApplicationCount(owner, field, value string) int {
return int(count)
}
func GetOrganizationApplicationCount(owner, Organization, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Application{Organization: Organization})
if err != nil {
panic(err)
}
return int(count)
}
func GetApplications(owner string) []*Application {
applications := []*Application{}
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner})
@ -95,8 +105,18 @@ func GetApplications(owner string) []*Application {
return applications
}
func GetPaginationApplications(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Application {
func GetOrganizationApplications(owner string, organization string) []*Application {
applications := []*Application{}
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
if err != nil {
panic(err)
}
return applications
}
func GetPaginationApplications(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Application {
var applications []*Application
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&applications)
if err != nil {
@ -106,9 +126,10 @@ func GetPaginationApplications(owner string, offset, limit int, field, value, so
return applications
}
func GetApplicationsByOrganizationName(owner string, organization string) []*Application {
func GetPaginationOrganizationApplications(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Application {
applications := []*Application{}
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&applications, &Application{Owner: owner, Organization: organization})
if err != nil {
panic(err)
}
@ -132,7 +153,7 @@ func getProviderMap(owner string) map[string]*Provider {
}
func extendApplicationWithProviders(application *Application) {
m := getProviderMap(application.Owner)
m := getProviderMap(application.Organization)
for _, providerItem := range application.Providers {
if provider, ok := m[providerItem.Name]; ok {
providerItem.Provider = provider
@ -381,7 +402,7 @@ func IsAllowOrigin(origin string) bool {
}
func getApplicationMap(organization string) map[string]*Application {
applications := GetApplicationsByOrganizationName("admin", organization)
applications := GetOrganizationApplications("admin", organization)
applicationMap := make(map[string]*Application)
for _, application := range applications {

View File

@ -15,7 +15,7 @@
package object
func (application *Application) GetProviderByCategory(category string) *Provider {
providers := GetProviders(application.Owner)
providers := GetProviders(application.Organization)
m := map[string]*Provider{}
for _, provider := range providers {
if provider.Category != category {

View File

@ -58,6 +58,7 @@ func initBuiltInOrganization() bool {
PhonePrefix: "86",
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{},
Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru"},
AccountItems: []*AccountItem{
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
@ -221,7 +222,7 @@ func initBuiltInLdap() {
}
func initBuiltInProvider() {
provider := GetProvider("admin/provider_captcha_default")
provider := GetProvider(util.GetId("provider_captcha_default"))
if provider != nil {
return
}

View File

@ -168,7 +168,7 @@ func initDefinedLdap(ldap *Ldap) {
}
func initDefinedProvider(provider *Provider) {
existed := GetProvider(provider.GetId())
existed := GetProvider(util.GetId(provider.Name))
if existed != nil {
return
}

View File

@ -45,6 +45,7 @@ type Organization struct {
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
Tags []string `xorm:"mediumtext" json:"tags"`
Languages []string `xorm:"varchar(255)" json:"languages"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
@ -222,7 +223,12 @@ func GetDefaultApplication(id string) (*Application, error) {
}
if organization.DefaultApplication != "" {
return getApplication("admin", organization.DefaultApplication), fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
defaultApplication := getApplication("admin", organization.DefaultApplication)
if defaultApplication == nil {
return nil, fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
} else {
return defaultApplication, nil
}
}
applications := []*Application{}

View File

@ -25,7 +25,7 @@ import (
type Provider struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
Name string `xorm:"varchar(100) notnull pk unique" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
@ -93,8 +93,8 @@ func GetMaskedProviders(providers []*Provider) []*Provider {
}
func GetProviderCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Provider{})
session := GetSession("", -1, -1, field, value, "", "")
count, err := session.Where("owner = ? or owner = ? ", "admin", owner).Count(&Provider{})
if err != nil {
panic(err)
}
@ -114,7 +114,7 @@ func GetGlobalProviderCount(field, value string) int {
func GetProviders(owner string) []*Provider {
providers := []*Provider{}
err := adapter.Engine.Desc("created_time").Find(&providers, &Provider{Owner: owner})
err := adapter.Engine.Where("owner = ? or owner = ? ", "admin", owner).Desc("created_time").Find(&providers, &Provider{})
if err != nil {
panic(err)
}
@ -133,9 +133,9 @@ func GetGlobalProviders() []*Provider {
}
func GetPaginationProviders(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Provider {
var providers []*Provider
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&providers)
providers := []*Provider{}
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Where("owner = ? or owner = ? ", "admin", owner).Find(&providers)
if err != nil {
panic(err)
}
@ -144,7 +144,7 @@ func GetPaginationProviders(owner string, offset, limit int, field, value, sortF
}
func GetPaginationGlobalProviders(offset, limit int, field, value, sortField, sortOrder string) []*Provider {
var providers []*Provider
providers := []*Provider{}
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&providers)
if err != nil {
@ -159,7 +159,7 @@ func getProvider(owner string, name string) *Provider {
return nil
}
provider := Provider{Owner: owner, Name: name}
provider := Provider{Name: name}
existed, err := adapter.Engine.Get(&provider)
if err != nil {
panic(err)

View File

@ -15,7 +15,9 @@
package object
type ProviderItem struct {
Name string `json:"name"`
Owner string `json:"owner"`
Name string `json:"name"`
CanSignUp bool `json:"canSignUp"`
CanSignIn bool `json:"canSignIn"`
CanUnlink bool `json:"canUnlink"`

View File

@ -420,6 +420,8 @@ class App extends Component {
</Link>
</Menu.Item>
);
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/applications">
<Link to="/applications">
@ -427,9 +429,6 @@ class App extends Component {
</Link>
</Menu.Item>
);
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/providers">
<Link to="/providers">
@ -565,10 +564,9 @@ class App extends Component {
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
@ -636,7 +634,7 @@ class App extends Component {
{
this.renderAccount()
}
<SelectLanguageBox />
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div>
</Header>
<Layout style={{backgroundColor: "#f5f5f5", alignItems: "stretch"}}>
@ -680,7 +678,7 @@ class App extends Component {
{
this.renderAccount()
}
<SelectLanguageBox />
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div>
</Header>
{

View File

@ -59,12 +59,6 @@
height: 70px; /* Footer height */
}
#language-box-corner {
position: absolute;
top: 75px;
right: 0;
}
.language-box {
background: url("@{StaticBaseUrl}/img/muti_language.svg");
background-size: 25px, 25px;

View File

@ -91,7 +91,7 @@ class ApplicationEditPage extends React.Component {
super(props);
this.state = {
classes: props,
owner: props.account.owner,
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
applicationName: props.match.params.applicationName,
application: null,
organizations: [],
@ -142,21 +142,11 @@ class ApplicationEditPage extends React.Component {
}
getProviders() {
if (Setting.isAdminUser(this.props.account)) {
ProviderBackend.getGlobalProviders()
.then((res) => {
this.setState({
providers: res,
});
});
} else {
ProviderBackend.getProviders(this.state.owner)
.then((res) => {
this.setState({
providers: res,
});
});
}
ProviderBackend.getProviders(this.state.owner).then((res => {
this.setState({
providers: res,
});
}));
}
getSamlMetadata() {
@ -287,7 +277,7 @@ class ApplicationEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.application.organization} onChange={(value => {this.updateApplicationField("organization", value);})}>
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.application.organization} onChange={(value => {this.updateApplicationField("organization", value);})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}
@ -791,7 +781,7 @@ class ApplicationEditPage extends React.Component {
submitApplicationEdit(willExist) {
const application = Setting.deepCopy(this.state.application);
ApplicationBackend.updateApplication(this.state.application.owner, this.state.applicationName, application)
ApplicationBackend.updateApplication("admin", this.state.applicationName, application)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
@ -802,7 +792,7 @@ class ApplicationEditPage extends React.Component {
if (willExist) {
this.props.history.push("/applications");
} else {
this.props.history.push(`/applications/${this.state.application.name}`);
this.props.history.push(`/applications/${this.state.application.organization}/${this.state.application.name}`);
}
} else {
Setting.showMessage("error", res.msg);

View File

@ -23,11 +23,28 @@ import i18next from "i18next";
import BaseListPage from "./BaseListPage";
class ApplicationListPage extends BaseListPage {
constructor(props) {
super(props);
this.state = {
classes: props,
organizationName: props.account.owner,
data: [],
pagination: {
current: 1,
pageSize: 10,
},
loading: false,
searchText: "",
searchedColumn: "",
};
}
newApplication() {
const randomName = Setting.getRandomName();
return {
owner: "admin", // this.props.account.applicationname,
owner: "admin", // this.props.account.applicationName,
name: `application_${randomName}`,
organization: this.state.organizationName,
createdTime: moment().format(),
displayName: `New Application - ${randomName}`,
logo: `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`,
@ -61,7 +78,7 @@ class ApplicationListPage extends BaseListPage {
const newApplication = this.newApplication();
ApplicationBackend.addApplication(newApplication)
.then((res) => {
this.props.history.push({pathname: `/applications/${newApplication.name}`, mode: "add"});
this.props.history.push({pathname: `/applications/${newApplication.organization}/${newApplication.name}`, mode: "add"});
}
)
.catch(error => {
@ -96,7 +113,7 @@ class ApplicationListPage extends BaseListPage {
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/applications/${text}`}>
<Link to={`/applications/${record.organization}/${text}`}>
{text}
</Link>
);
@ -213,7 +230,7 @@ class ApplicationListPage extends BaseListPage {
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/applications/${record.organization}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete application: ${record.name} ?`}
onConfirm={() => this.deleteApplication(index)}
@ -254,7 +271,8 @@ class ApplicationListPage extends BaseListPage {
const field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({loading: true});
ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
(Setting.isAdminUser(this.props.account) ? ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) :
ApplicationBackend.getApplicationsByOrganization("admin", this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@ -255,6 +255,31 @@ class OrganizationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Languages"), i18next.t("general:Languages - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}}
value={this.state.organization.languages}
onChange={(value => {
this.updateOrganizationField("languages", value);
})} >
{
[
{value: "en", label: "English"},
{value: "zh", label: "简体中文"},
{value: "es", label: "Español"},
{value: "fr", label: "Français"},
{value: "de", label: "Deutsch"},
{value: "ja", label: "日本語"},
{value: "ko", label: "한국어"},
{value: "ru", label: "Русский"},
].map((item, index) => <Option key={index} value={item.value}>{item.label}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} :

View File

@ -37,6 +37,7 @@ class OrganizationListPage extends BaseListPage {
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
defaultApplication: "",
tags: [],
languages: ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
masterPassword: "",
enableSoftDeletion: false,
isProfilePublic: true,

View File

@ -29,7 +29,7 @@ class PermissionListPage extends BaseListPage {
name: `permission_${randomName}`,
createdTime: moment().format(),
displayName: `New Permission - ${randomName}`,
users: [this.props.account.name],
users: [`${this.props.account.owner}/${this.props.account.name}`],
roles: [],
domains: [],
resourceType: "Application",

View File

@ -22,6 +22,7 @@ import {authConfig} from "./auth/Auth";
import * as ProviderEditTestEmail from "./TestEmailWidget";
import copy from "copy-to-clipboard";
import {CaptchaPreview} from "./common/CaptchaPreview";
import * as OrganizationBackend from "./backend/OrganizationBackend";
const {Option} = Select;
const {TextArea} = Input;
@ -34,11 +35,13 @@ class ProviderEditPage extends React.Component {
providerName: props.match.params.providerName,
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
provider: null,
organizations: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
UNSAFE_componentWillMount() {
this.getOrganizations();
this.getProvider();
}
@ -51,6 +54,17 @@ class ProviderEditPage extends React.Component {
});
}
getOrganizations() {
if (Setting.isAdminUser(this.props.account)) {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: res.msg === undefined ? res : [],
});
});
}
}
parseProviderField(key, value) {
if (["port"].includes(key)) {
value = Setting.myParseInt(value);
@ -191,6 +205,19 @@ class ProviderEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.provider.owner} onChange={(value => {this.updateProviderField("owner", value);})}>
{Setting.isAdminUser(this.props.account) ? <Option key={"admin"} value={"admin"}>{i18next.t("provider:admin (share)")}</Option> : null}
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
@ -760,18 +787,19 @@ class ProviderEditPage extends React.Component {
submitProviderEdit(willExist) {
const provider = Setting.deepCopy(this.state.provider);
ProviderBackend.updateProvider(this.state.provider.owner, this.state.providerName, provider)
ProviderBackend.updateProvider(this.state.owner, this.state.providerName, provider)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
this.setState({
owner: this.state.provider.owner,
providerName: this.state.provider.name,
});
if (willExist) {
this.props.history.push("/providers");
} else {
this.props.history.push(`/providers/${this.state.provider.name}`);
this.props.history.push(`/providers/${this.state.provider.owner}/${this.state.provider.name}`);
}
} else {
Setting.showMessage("error", res.msg);

View File

@ -27,7 +27,7 @@ class ProviderListPage extends BaseListPage {
super(props);
this.state = {
classes: props,
owner: Setting.isAdminUser(props.account) ? "admin" : props.account.organization.name,
owner: Setting.isAdminUser(props.account) ? "admin" : props.account.owner,
data: [],
pagination: {
current: 1,
@ -96,12 +96,20 @@ class ProviderListPage extends BaseListPage {
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/providers/${text}`}>
<Link to={`/providers/${record.owner}/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Organization"),
dataIndex: "owner",
key: "owner",
width: "150px",
sorter: true,
...this.getColumnSearchProps("owner"),
},
{
title: i18next.t("general:Created time"),
dataIndex: "createdTime",
@ -192,12 +200,12 @@ class ProviderListPage extends BaseListPage {
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete provider: ${record.name} ?`}
onConfirm={() => this.deleteProvider(index)}
>
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -95,7 +95,7 @@ class ResourceListPage extends BaseListPage {
...this.getColumnSearchProps("provider"),
render: (text, record, index) => {
return (
<Link to={`/providers/${text}`}>
<Link to={`/providers/${record.owner}/${text}`}>
{text}
</Link>
);

View File

@ -28,28 +28,45 @@ class SelectLanguageBox extends React.Component {
super(props);
this.state = {
classes: props,
languages: props.languages ?? ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
};
}
items = [
this.getItem("English", "en", flagIcon("US", "English")),
this.getItem("简体中文", "zh", flagIcon("CN", "简体中文")),
this.getItem("Español", "es", flagIcon("ES", "Español")),
this.getItem("Français", "fr", flagIcon("FR", "Français")),
this.getItem("Deutsch", "de", flagIcon("DE", "Deutsch")),
this.getItem("日本語", "ja", flagIcon("JP", "日本語")),
this.getItem("한국어", "ko", flagIcon("KR", "한국어")),
this.getItem("Русский", "ru", flagIcon("RU", "Русский")),
];
getOrganizationLanguages(languages) {
const select = [];
for (const language of languages) {
this.items.map((item, index) => item.key === language ? select.push(item) : null);
}
return select;
}
getItem(label, key, icon) {
return {key, icon, label};
}
render() {
const languageItems = this.getOrganizationLanguages(this.state.languages);
const menu = (
<Menu onClick={(e) => {
Setting.changeLanguage(e.key);
<Menu items={languageItems} onClick={(e) => {
Setting.setLanguage(e.key);
}}>
<Menu.Item key="en" icon={flagIcon("US", "English")}>English</Menu.Item>
<Menu.Item key="zh" icon={flagIcon("CN", "简体中文")}>简体中文</Menu.Item>
<Menu.Item key="es" icon={flagIcon("ES", "Español")}>Español</Menu.Item>
<Menu.Item key="fr" icon={flagIcon("FR", "Français")}>Français</Menu.Item>
<Menu.Item key="de" icon={flagIcon("DE", "Deutsch")}>Deutsch</Menu.Item>
<Menu.Item key="ja" icon={flagIcon("JP", "日本語")}>日本語</Menu.Item>
<Menu.Item key="ko" icon={flagIcon("KR", "한국어")}>한국어</Menu.Item>
<Menu.Item key="ru" icon={flagIcon("RU", "Русский")}>Русский</Menu.Item>
</Menu>
);
return (
<Dropdown overlay={menu} >
<div className="language-box" id={this.props.id} style={this.props.style} />
<div className="language-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} />
</Dropdown>
);
}

View File

@ -552,13 +552,6 @@ export function setLanguage(language) {
i18next.changeLanguage(language);
}
export function changeLanguage(language) {
localStorage.setItem("language", language);
changeMomentLanguage(language);
i18next.changeLanguage(language);
// window.location.reload(true);
}
export function getAcceptLanguage() {
return i18next.language + ";q=0.9,en;q=0.8";
}

View File

@ -102,7 +102,7 @@ class TokenListPage extends BaseListPage {
...this.getColumnSearchProps("application"),
render: (text, record, index) => {
return (
<Link to={`/applications/${text}`}>
<Link to={`/applications/${record.organization}/${text}`}>
{text}
</Link>
);

View File

@ -167,7 +167,7 @@ class UserListPage extends BaseListPage {
...this.getColumnSearchProps("signupApplication"),
render: (text, record, index) => {
return (
<Link to={`/applications/${text}`}>
<Link to={`/applications/${record.owner}/${text}`}>
{text}
</Link>
);

View File

@ -54,7 +54,7 @@ export function oAuthParamsToQuery(oAuthParams) {
}
// code
return `?clientId=${oAuthParams.clientId}&responseType=${oAuthParams.responseType}&redirectUri=${oAuthParams.redirectUri}&scope=${oAuthParams.scope}&state=${oAuthParams.state}&nonce=${oAuthParams.nonce}&code_challenge_method=${oAuthParams.challengeMethod}&code_challenge=${oAuthParams.codeChallenge}`;
return `?clientId=${oAuthParams.clientId}&responseType=${oAuthParams.responseType}&redirectUri=${encodeURIComponent(oAuthParams.redirectUri)}&scope=${oAuthParams.scope}&state=${oAuthParams.state}&nonce=${oAuthParams.nonce}&code_challenge_method=${oAuthParams.challengeMethod}&code_challenge=${oAuthParams.codeChallenge}`;
}
export function getApplicationLogin(oAuthParams) {

View File

@ -28,9 +28,7 @@ import i18next from "i18next";
import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox";
import {withTranslation} from "react-i18next";
import {CaptchaModal} from "../common/CaptchaModal";
import {withRouter} from "react-router-dom";
const {TabPane} = Tabs;
@ -800,7 +798,7 @@ class LoginPage extends React.Component {
{/* {*/}
{/* this.state.clientId !== null ? "Redirect" : null*/}
{/* }*/}
<SelectLanguageBox id="language-box-corner" style={{top: "55px", right: "5px", position: "absolute"}} />
<SelectLanguageBox languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
{
this.renderSignedInBox()
}
@ -817,4 +815,4 @@ class LoginPage extends React.Component {
}
}
export default withTranslation()(withRouter(LoginPage));
export default LoginPage;

View File

@ -118,7 +118,7 @@ function getSamlUrl(provider, location) {
export function renderProviderLogo(provider, application, width, margin, size, location) {
if (size === "small") {
if (provider.category === "OAuth") {
if (provider.type === "WeChat" && provider.clientId2 !== "" && provider.clientSecret2 !== "" && provider.content !== "" && provider.disableSsl === true) {
if (provider.type === "WeChat" && provider.clientId2 !== "" && provider.clientSecret2 !== "" && provider.content !== "" && provider.disableSsl === true && !navigator.userAgent.includes("MicroMessenger")) {
const info = async() => {
const t1 = setInterval(await getEvent, 1000, application, provider);
{Modal.info({

View File

@ -645,7 +645,7 @@ class SignupPage extends React.Component {
{
Setting.renderLogo(application)
}
<SelectLanguageBox id="language-box-corner" style={{top: "55px", right: "5px", position: "absolute"}} />
<SelectLanguageBox languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
{
this.renderForm(application)
}

View File

@ -24,8 +24,8 @@ export function getApplications(owner, page = "", pageSize = "", field = "", val
}).then(res => res.json());
}
export function getApplicationsByOrganization(owner, organization) {
return fetch(`${Setting.ServerUrl}/api/get-organization-applications?owner=${owner}&organization=${organization}`, {
export function getApplicationsByOrganization(owner, organization, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-organization-applications?owner=${owner}&organization=${organization}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
headers: {
@ -68,7 +68,6 @@ export function updateApplication(owner, name, application) {
export function addApplication(application) {
const newApplication = Setting.deepCopy(application);
newApplication.organization = "built-in";
return fetch(`${Setting.ServerUrl}/api/add-application`, {
method: "POST",
credentials: "include",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Ist aktiviert - Tooltip",
"LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Tooltip",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Is enabled - Tooltip",
"LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Tooltip",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "Logo - Tooltip",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Est activé - infobulle",
"LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Infobulle",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "有効にする - ツールチップ",
"LDAPs": "LDAP",
"LDAPs - Tooltip": "LDAP - ツールチップ",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Is enabled - Tooltip",
"LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Tooltip",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Включено - Подсказка",
"LDAPs": "LDAPы",
"LDAPs - Tooltip": "LDAPs - Подсказки",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Фамилия",
"Logo": "Логотип",
"Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "是否启用",
"LDAPs": "LDAP",
"LDAPs - Tooltip": "LDAPs",
"Languages": "语言",
"Languages - Tooltip": "可选语言",
"Last name": "姓氏",
"Logo": "Logo",
"Logo - Tooltip": "应用程序向外展示的图标",