mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-23 22:53:31 +08:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
53243a30f3 | |||
cbdeb91ee8 | |||
2dd1dc582f | |||
f3d4b45a0f | |||
2ee4aebd96 | |||
150e3e30d5 | |||
1055d7781b | |||
1c296e9b6f |
@ -19,6 +19,7 @@ origin =
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
enableGzip = true
|
||||
ldapServerPort = 389
|
||||
radiusServerPort = 1812
|
||||
radiusSecret = "secret"
|
||||
|
@ -33,7 +33,13 @@ func (c *ApiController) GetSamlMeta() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
|
||||
return
|
||||
}
|
||||
metadata, _ := object.GetSamlMeta(application, host)
|
||||
|
||||
metadata, err := object.GetSamlMeta(application, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["xml"] = metadata
|
||||
c.ServeXML()
|
||||
}
|
||||
|
@ -96,6 +96,13 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(userId, "app/") {
|
||||
tmpUserId := c.Input().Get("userId")
|
||||
if tmpUserId != "" {
|
||||
userId = tmpUserId
|
||||
}
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
|
@ -22,14 +22,15 @@ config: |
|
||||
dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1"
|
||||
dbName = casdoor
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = ""
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
origin = "https://door.casbin.com"
|
||||
origin =
|
||||
enableGzip = true
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
|
@ -25,11 +25,19 @@ import (
|
||||
)
|
||||
|
||||
type SignupItem struct {
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
Rule string `json:"rule"`
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
Label string `json:"label"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
|
||||
type SamlItem struct {
|
||||
Name string `json:"name"`
|
||||
NameFormat string `json:"nameformat"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Application struct {
|
||||
@ -54,12 +62,13 @@ type Application struct {
|
||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
|
||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
|
@ -370,7 +370,7 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
||||
continue
|
||||
}
|
||||
|
||||
if !permission.isUserHit(userId) {
|
||||
if !permission.isUserHit(userId) && !permission.isRoleHit(userId) {
|
||||
if permission.Effect == "Allow" {
|
||||
allowPermissionCount += 1
|
||||
} else {
|
||||
|
@ -434,6 +434,21 @@ func (p *Permission) isUserHit(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Permission) isRoleHit(userId string) bool {
|
||||
targetRoles, err := getRolesByUser(userId)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, role := range p.Roles {
|
||||
for _, targetRole := range targetRoles {
|
||||
if targetRole.GetId() == role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Permission) isResourceHit(name string) bool {
|
||||
for _, resource := range p.Resources {
|
||||
if resource == "*" || resource == name {
|
||||
|
@ -37,7 +37,7 @@ import (
|
||||
|
||||
// NewSamlResponse
|
||||
// returns a saml2 response
|
||||
func NewSamlResponse(user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
|
||||
func NewSamlResponse(application *Application, user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
|
||||
samlResponse := &etree.Element{
|
||||
Space: "samlp",
|
||||
Tag: "Response",
|
||||
@ -103,6 +103,13 @@ func NewSamlResponse(user *User, host string, certificate string, destination st
|
||||
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
|
||||
|
||||
for _, item := range application.SamlAttributes {
|
||||
role := attributes.CreateElement("saml:Attribute")
|
||||
role.CreateAttr("Name", item.Name)
|
||||
role.CreateAttr("NameFormat", item.NameFormat)
|
||||
role.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(item.Value)
|
||||
}
|
||||
|
||||
roles := attributes.CreateElement("saml:Attribute")
|
||||
roles.CreateAttr("Name", "Roles")
|
||||
roles.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
@ -184,10 +191,11 @@ type SingleSignOnService struct {
|
||||
|
||||
type Attribute struct {
|
||||
XMLName xml.Name
|
||||
Name string `xml:"Name,attr"`
|
||||
NameFormat string `xml:"NameFormat,attr"`
|
||||
FriendlyName string `xml:"FriendlyName,attr"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Name string `xml:"Name,attr"`
|
||||
NameFormat string `xml:"NameFormat,attr"`
|
||||
FriendlyName string `xml:"FriendlyName,attr"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Values []string `xml:"AttributeValue"`
|
||||
}
|
||||
|
||||
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
|
||||
@ -309,7 +317,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
// build signedResponse
|
||||
samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
||||
samlResponse, _ := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
||||
randomKeyStore := &X509Key{
|
||||
PrivateKey: cert.PrivateKey,
|
||||
X509Certificate: certificate,
|
||||
|
@ -35,14 +35,14 @@ type Object struct {
|
||||
func getUsername(ctx *context.Context) (username string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
username = getUsernameByClientIdSecret(ctx)
|
||||
username, _ = getUsernameByClientIdSecret(ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
username = ctx.Input.Session("username").(string)
|
||||
|
||||
if username == "" {
|
||||
username = getUsernameByClientIdSecret(ctx)
|
||||
username, _ = getUsernameByClientIdSecret(ctx)
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
|
@ -45,19 +45,21 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if token == nil {
|
||||
responseError(ctx, "Access token doesn't exist")
|
||||
responseError(ctx, "Access token doesn't exist in database")
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsTokenExpired(token.CreatedTime, token.ExpiresIn) {
|
||||
responseError(ctx, "Access token has expired")
|
||||
isExpired, expireTime := util.IsTokenExpired(token.CreatedTime, token.ExpiresIn)
|
||||
if isExpired {
|
||||
responseError(ctx, fmt.Sprintf("Access token has expired, expireTime = %s", expireTime))
|
||||
return
|
||||
}
|
||||
|
||||
userId := util.GetId(token.Organization, token.User)
|
||||
application, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
responseError(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
setSessionUser(ctx, userId)
|
||||
@ -66,7 +68,11 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
}
|
||||
|
||||
// "/page?clientId=123&clientSecret=456"
|
||||
userId := getUsernameByClientIdSecret(ctx)
|
||||
userId, err := getUsernameByClientIdSecret(ctx)
|
||||
if err != nil {
|
||||
responseError(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
if userId != "" {
|
||||
setSessionUser(ctx, userId)
|
||||
return
|
||||
|
@ -66,7 +66,7 @@ func denyRequest(ctx *context.Context) {
|
||||
responseError(ctx, T(ctx, "auth:Unauthorized operation"))
|
||||
}
|
||||
|
||||
func getUsernameByClientIdSecret(ctx *context.Context) string {
|
||||
func getUsernameByClientIdSecret(ctx *context.Context) (string, error) {
|
||||
clientId, clientSecret, ok := ctx.Request.BasicAuth()
|
||||
if !ok {
|
||||
clientId = ctx.Input.Query("clientId")
|
||||
@ -74,19 +74,22 @@ func getUsernameByClientIdSecret(ctx *context.Context) string {
|
||||
}
|
||||
|
||||
if clientId == "" || clientSecret == "" {
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
application, err := object.GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return "", err
|
||||
}
|
||||
if application == nil {
|
||||
return "", fmt.Errorf("Application not found for client ID: %s", clientId)
|
||||
}
|
||||
|
||||
if application == nil || application.ClientSecret != clientSecret {
|
||||
return ""
|
||||
if application.ClientSecret != clientSecret {
|
||||
return "", fmt.Errorf("Incorrect client secret for application: %s", application.Name)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("app/%s", application.Name)
|
||||
return fmt.Sprintf("app/%s", application.Name), nil
|
||||
}
|
||||
|
||||
func getUsernameByKeys(ctx *context.Context) string {
|
||||
|
@ -58,8 +58,10 @@ func Time2String(timestamp time.Time) string {
|
||||
return timestamp.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func IsTokenExpired(createdTime string, expiresIn int) bool {
|
||||
func IsTokenExpired(createdTime string, expiresIn int) (bool, string) {
|
||||
createdTimeObj, _ := time.Parse(time.RFC3339, createdTime)
|
||||
expiresAtObj := createdTimeObj.Add(time.Duration(expiresIn) * time.Second)
|
||||
return time.Now().After(expiresAtObj)
|
||||
isExpired := time.Now().After(expiresAtObj)
|
||||
expireTime := expiresAtObj.Local().Format(time.RFC3339)
|
||||
return isExpired, expireTime
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ func Test_IsTokenExpired(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(scenario.description, func(t *testing.T) {
|
||||
result := IsTokenExpired(scenario.input.createdTime, scenario.input.expiresIn)
|
||||
result, _ := IsTokenExpired(scenario.input.createdTime, scenario.input.expiresIn)
|
||||
assert.Equal(t, scenario.expected, result, fmt.Sprintf("Expected %t, but was founded %t", scenario.expected, result))
|
||||
})
|
||||
}
|
||||
|
@ -28,12 +28,13 @@ import i18next from "i18next";
|
||||
import UrlTable from "./table/UrlTable";
|
||||
import ProviderTable from "./table/ProviderTable";
|
||||
import SignupTable from "./table/SignupTable";
|
||||
import SamlAttributeTable from "./table/SamlAttributeTable";
|
||||
import PromptPage from "./auth/PromptPage";
|
||||
import copy from "copy-to-clipboard";
|
||||
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||
|
||||
require("codemirror/theme/material-darker.css");
|
||||
require("codemirror/mode/htmlmixed/htmlmixed");
|
||||
@ -104,6 +105,7 @@ class ApplicationEditPage extends React.Component {
|
||||
providers: [],
|
||||
uploading: false,
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
samlAttributes: [],
|
||||
samlMetadata: null,
|
||||
isAuthorized: true,
|
||||
};
|
||||
@ -638,6 +640,19 @@ class ApplicationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:SAML Attribute"), i18next.t("general:SAML Attribute - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<SamlAttributeTable
|
||||
title={i18next.t("general:SAML Attribute")}
|
||||
table={this.state.application.samlAttributes}
|
||||
application={this.state.application}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("samlAttributes", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} :
|
||||
|
@ -226,7 +226,7 @@ class SignupPage extends React.Component {
|
||||
return (
|
||||
<Form.Item
|
||||
name="username"
|
||||
label={i18next.t("signup:Username")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("signup:Username")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -235,7 +235,7 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Input placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signupItem.name === "Display name") {
|
||||
@ -244,7 +244,7 @@ class SignupPage extends React.Component {
|
||||
<React.Fragment>
|
||||
<Form.Item
|
||||
name="firstName"
|
||||
label={i18next.t("general:First name")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("general:First name")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -253,11 +253,11 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Input placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="lastName"
|
||||
label={i18next.t("general:Last name")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("general:Last name")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -266,7 +266,7 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Input placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
</React.Fragment>
|
||||
);
|
||||
@ -275,7 +275,7 @@ class SignupPage extends React.Component {
|
||||
return (
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={(signupItem.rule === "Real name" || signupItem.rule === "First, last") ? i18next.t("general:Real name") : i18next.t("general:Display name")}
|
||||
label={(signupItem.label ? signupItem.label : (signupItem.rule === "Real name" || signupItem.rule === "First, last") ? i18next.t("general:Real name") : i18next.t("general:Display name"))}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -284,14 +284,14 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Input placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signupItem.name === "Affiliation") {
|
||||
return (
|
||||
<Form.Item
|
||||
name="affiliation"
|
||||
label={i18next.t("user:Affiliation")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("user:Affiliation")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -300,14 +300,14 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Input placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signupItem.name === "ID card") {
|
||||
return (
|
||||
<Form.Item
|
||||
name="idCard"
|
||||
label={i18next.t("user:ID card")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("user:ID card")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -321,14 +321,14 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Input placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signupItem.name === "Country/Region") {
|
||||
return (
|
||||
<Form.Item
|
||||
name="country_region"
|
||||
label={i18next.t("user:Country/Region")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("user:Country/Region")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -344,7 +344,7 @@ class SignupPage extends React.Component {
|
||||
<React.Fragment>
|
||||
<Form.Item
|
||||
name="email"
|
||||
label={i18next.t("general:Email")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("general:Email")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -363,13 +363,13 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input onChange={e => this.setState({email: e.target.value})} />
|
||||
<Input placeholder={signupItem.placeholder} onChange={e => this.setState({email: e.target.value})} />
|
||||
</Form.Item>
|
||||
{
|
||||
signupItem.rule !== "No verification" &&
|
||||
<Form.Item
|
||||
name="emailCode"
|
||||
label={i18next.t("code:Email code")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("code:Email code")}
|
||||
rules={[{
|
||||
required: required,
|
||||
message: i18next.t("code:Please input your verification code!"),
|
||||
@ -388,7 +388,7 @@ class SignupPage extends React.Component {
|
||||
} else if (signupItem.name === "Phone") {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Form.Item label={i18next.t("general:Phone")} required={required}>
|
||||
<Form.Item label={signupItem.label ? signupItem.label : i18next.t("general:Phone")} required={required}>
|
||||
<Input.Group compact>
|
||||
<Form.Item
|
||||
name="countryCode"
|
||||
@ -432,6 +432,7 @@ class SignupPage extends React.Component {
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder={signupItem.placeholder}
|
||||
style={{width: "65%"}}
|
||||
onChange={e => this.setState({phone: e.target.value})}
|
||||
/>
|
||||
@ -442,7 +443,7 @@ class SignupPage extends React.Component {
|
||||
signupItem.rule !== "No verification" &&
|
||||
<Form.Item
|
||||
name="phoneCode"
|
||||
label={i18next.t("code:Phone code")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("code:Phone code")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -465,7 +466,7 @@ class SignupPage extends React.Component {
|
||||
return (
|
||||
<Form.Item
|
||||
name="password"
|
||||
label={i18next.t("general:Password")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("general:Password")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -482,14 +483,14 @@ class SignupPage extends React.Component {
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password />
|
||||
<Input.Password placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signupItem.name === "Confirm password") {
|
||||
return (
|
||||
<Form.Item
|
||||
name="confirm"
|
||||
label={i18next.t("signup:Confirm")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("signup:Confirm")}
|
||||
dependencies={["password"]}
|
||||
hasFeedback
|
||||
rules={[
|
||||
@ -508,14 +509,14 @@ class SignupPage extends React.Component {
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.Password />
|
||||
<Input.Password placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signupItem.name === "Invitation code") {
|
||||
return (
|
||||
<Form.Item
|
||||
name="invitationCode"
|
||||
label={i18next.t("application:Invitation code")}
|
||||
label={signupItem.label ? signupItem.label : i18next.t("application:Invitation code")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
@ -523,11 +524,15 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Input placeholder={signupItem.placeholder} />
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signupItem.name === "Agreement") {
|
||||
return AgreementModal.renderAgreementFormItem(application, required, tailFormItemLayout, this);
|
||||
} else if (signupItem.name.startsWith("Text ")) {
|
||||
return (
|
||||
<div dangerouslySetInnerHTML={{__html: signupItem.label}} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
162
web/src/table/SamlAttributeTable.js
Normal file
162
web/src/table/SamlAttributeTable.js
Normal file
@ -0,0 +1,162 @@
|
||||
// Copyright 2023 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 {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||
import {Button, Col, Input, Row, Select, Table, Tooltip} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
class SamlAttributeTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
};
|
||||
}
|
||||
|
||||
updateTable(table) {
|
||||
this.props.onUpdateTable(table);
|
||||
}
|
||||
|
||||
updateField(table, index, key, value) {
|
||||
table[index][key] = value;
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
addRow(table) {
|
||||
const row = {Name: "", nameformat: "", value: ""};
|
||||
if (table === undefined || table === null) {
|
||||
table = [];
|
||||
}
|
||||
table = Setting.addRow(table, row);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
deleteRow(table, i) {
|
||||
table = Setting.deleteRow(table, i);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
upRow(table, i) {
|
||||
table = Setting.swapRow(table, i - 1, i);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
downRow(table, i) {
|
||||
table = Setting.swapRow(table, i, i + 1);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
renderTable(table) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("user:Name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, "name", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("user:Name format"),
|
||||
dataIndex: "nameformat",
|
||||
key: "nameformat",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}}
|
||||
value={text}
|
||||
defaultValue="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"
|
||||
onChange={value => {
|
||||
this.updateField(table, index, "nameformat", value);
|
||||
}} >
|
||||
<Option key="Unspecified" value="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">{i18next.t("general:Unspecified")}</Option>
|
||||
<Option key="Basic" value="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">{i18next.t("application:Basic")}</Option>
|
||||
<Option key="UriReference" value="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">{i18next.t("application:UriReference")}</Option>
|
||||
<Option key="x500AttributeName" value="urn:oasis:names:tc:SAML:2.0:attrname-format:X500">{i18next.t("application:x500AttributeName")}</Option>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("user:Value"),
|
||||
dataIndex: "value",
|
||||
key: "value",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, "value", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "action",
|
||||
key: "action",
|
||||
width: "20px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
|
||||
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
|
||||
</Tooltip>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
|
||||
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
|
||||
</Tooltip>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
|
||||
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table title={() => (
|
||||
<div>
|
||||
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col span={24}>
|
||||
{
|
||||
this.renderTable(this.props.table)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SamlAttributeTable;
|
@ -14,10 +14,16 @@
|
||||
|
||||
import React from "react";
|
||||
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||
import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
|
||||
import {Button, Col, Input, Popover, Row, Select, Switch, Table, Tooltip} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
|
||||
require("codemirror/theme/material-darker.css");
|
||||
require("codemirror/mode/htmlmixed/htmlmixed");
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
class SignupTable extends React.Component {
|
||||
@ -81,6 +87,11 @@ class SignupTable extends React.Component {
|
||||
{name: "Phone", displayName: i18next.t("general:Phone")},
|
||||
{name: "Invitation code", displayName: i18next.t("application:Invitation code")},
|
||||
{name: "Agreement", displayName: i18next.t("signup:Agreement")},
|
||||
{name: "Text 1", displayName: i18next.t("signup:Text 1")},
|
||||
{name: "Text 2", displayName: i18next.t("signup:Text 2")},
|
||||
{name: "Text 3", displayName: i18next.t("signup:Text 3")},
|
||||
{name: "Text 4", displayName: i18next.t("signup:Text 4")},
|
||||
{name: "Text 5", displayName: i18next.t("signup:Text 5")},
|
||||
];
|
||||
|
||||
const getItemDisplayName = (text) => {
|
||||
@ -164,6 +175,55 @@ class SignupTable extends React.Component {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("signup:Label"),
|
||||
dataIndex: "label",
|
||||
key: "label",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
if (record.name.startsWith("Text ")) {
|
||||
return (
|
||||
<Popover placement="right" content={
|
||||
<div style={{width: "900px", height: "300px"}} >
|
||||
<CodeMirror value={text}
|
||||
options={{mode: "htmlmixed", theme: "material-darker"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
this.updateField(table, index, "label", value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
} title={i18next.t("signup:Label HTML")} trigger="click">
|
||||
<Input value={text} style={{marginBottom: "10px"}} onChange={e => {
|
||||
this.updateField(table, index, "label", e.target.value);
|
||||
}} />
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, "label", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("signup:Placeholder"),
|
||||
dataIndex: "placeholder",
|
||||
key: "placeholder",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
if (record.name.startsWith("Text ")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, "placeholder", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("application:Rule"),
|
||||
dataIndex: "rule",
|
||||
|
Reference in New Issue
Block a user