Compare commits

...

5 Commits

9 changed files with 92 additions and 40 deletions

View File

@@ -40,6 +40,18 @@ func (c *ApiController) Enforce() {
enforcerId := c.Input().Get("enforcerId")
owner := c.Input().Get("owner")
params := []string{permissionId, modelId, resourceId, enforcerId, owner}
nonEmpty := 0
for _, param := range params {
if param != "" {
nonEmpty++
}
}
if nonEmpty > 1 {
c.ResponseError("Only one of the parameters (permissionId, modelId, resourceId, enforcerId, owner) should be provided")
return
}
if len(c.Ctx.Input.RequestBody) == 0 {
c.ResponseError("The request body should not be empty")
return
@@ -169,6 +181,18 @@ func (c *ApiController) BatchEnforce() {
enforcerId := c.Input().Get("enforcerId")
owner := c.Input().Get("owner")
params := []string{permissionId, modelId, enforcerId, owner}
nonEmpty := 0
for _, param := range params {
if param != "" {
nonEmpty++
}
}
if nonEmpty > 1 {
c.ResponseError("Only one of the parameters (permissionId, modelId, enforcerId, owner) should be provided")
return
}
var requests [][]string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
if err != nil {

1
go.mod
View File

@@ -52,7 +52,6 @@ require (
github.com/sendgrid/sendgrid-go v3.14.0+incompatible
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.10.0
github.com/stripe/stripe-go/v74 v74.29.0
github.com/tealeg/xlsx v1.0.5

2
go.sum
View File

@@ -872,8 +872,6 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/slack-go/slack v0.12.3 h1:92/dfFU8Q5XP6Wp5rr5/T5JHLM5c5Smtn53fhToAP88=
github.com/slack-go/slack v0.12.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"github.com/casdoor/casdoor/util"
"github.com/mitchellh/mapstructure"
@@ -64,6 +65,25 @@ func (idp *CustomIdProvider) GetToken(code string) (*oauth2.Token, error) {
return idp.Config.Exchange(ctx, code)
}
func getNestedValue(data map[string]interface{}, path string) (interface{}, error) {
keys := strings.Split(path, ".")
var val interface{} = data
for _, key := range keys {
m, ok := val.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("path '%s' is not valid: %s is not a map", path, key)
}
val, ok = m[key]
if !ok {
return nil, fmt.Errorf("key '%s' not found in path '%s'", key, path)
}
}
return val, nil
}
type CustomUserInfo struct {
Id string `mapstructure:"id"`
Username string `mapstructure:"username"`
@@ -108,11 +128,11 @@ func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
// map user info
for k, v := range idp.UserMapping {
_, ok := dataMap[v]
if !ok {
return nil, fmt.Errorf("cannot find %s in user from custom provider", v)
val, err := getNestedValue(dataMap, v)
if err != nil {
return nil, fmt.Errorf("cannot find %s in user from custom provider: %v", v, err)
}
dataMap[k] = dataMap[v]
dataMap[k] = val
}
// try to parse id to string

View File

@@ -17,7 +17,6 @@ package idp
import (
"bytes"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
@@ -29,7 +28,6 @@ import (
"sync"
"time"
"github.com/skip2/go-qrcode"
"golang.org/x/oauth2"
)
@@ -324,10 +322,7 @@ func GetWechatOfficialAccountQRCode(clientId string, clientSecret string, provid
return "", "", err
}
var png []byte
png, err = qrcode.Encode(data.URL, qrcode.Medium, 256)
base64Image := base64.StdEncoding.EncodeToString(png)
return base64Image, data.Ticket, nil
return data.URL, data.Ticket, nil
}
func VerifyWechatSignature(token string, nonce string, timestamp string, signature string) bool {

View File

@@ -125,10 +125,10 @@ func getPolicies(permission *Permission) [][]string {
for _, action := range permission.Actions {
if domainExist {
for _, domain := range permission.Domains {
policies = append(policies, []string{userOrRole, domain, resource, strings.ToLower(action), strings.ToLower(permission.Effect), permissionId})
policies = append(policies, []string{userOrRole, domain, resource, action, strings.ToLower(permission.Effect), permissionId})
}
} else {
policies = append(policies, []string{userOrRole, resource, strings.ToLower(action), strings.ToLower(permission.Effect), "", permissionId})
policies = append(policies, []string{userOrRole, resource, action, strings.ToLower(permission.Effect), "", permissionId})
}
}
}

View File

@@ -342,10 +342,31 @@ func getClaimsCustom(claims Claims, tokenField []string) jwt.MapClaims {
res["provider"] = claims.Provider
for _, field := range tokenField {
userField := userValue.FieldByName(field)
if userField.IsValid() {
newfield := util.SnakeToCamel(util.CamelToSnakeCase(field))
res[newfield] = userField.Interface()
if strings.HasPrefix(field, "Properties") {
/*
Use selected properties fields as custom claims.
Converts `Properties.my_field` to custom claim with name `my_field`.
*/
parts := strings.Split(field, ".")
if len(parts) != 2 || parts[0] != "Properties" { // Either too many segments, or not properly scoped to `Properties`, so skip.
continue
}
base, fieldName := parts[0], parts[1]
mField := userValue.FieldByName(base)
if !mField.IsValid() { // Can't find `Properties` field, so skip.
continue
}
finalField := mField.MapIndex(reflect.ValueOf(fieldName))
if finalField.IsValid() { // // Provided field within `Properties` exists, add claim.
res[fieldName] = finalField.Interface()
}
} else { // Use selected user field as claims.
userField := userValue.FieldByName(field)
if userField.IsValid() {
newfield := util.SnakeToCamel(util.CamelToSnakeCase(field))
res[newfield] = userField.Interface()
}
}
}

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Alert, Button, Modal, Result} from "antd";
import {Alert, Button, Modal, QRCode, Result} from "antd";
import i18next from "i18next";
import {getWechatMessageEvent} from "./AuthBackend";
import * as Setting from "../Setting";
@@ -217,7 +217,7 @@ export async function WechatOfficialAccountModal(application, provider, method)
title: i18next.t("provider:Please use WeChat to scan the QR code and follow the official account for sign in"),
content: (
<div style={{marginRight: "34px"}}>
<img src = {"data:image/png;base64," + res.data} alt="Wechat QR code" style={{width: "100%"}} />
<QRCode style={{padding: "20px", margin: "auto"}} bordered={false} value={res.data} size={230} />
</div>
),
onOk() {

View File

@@ -16,13 +16,14 @@ import React from "react";
import * as AuthBackend from "./AuthBackend";
import i18next from "i18next";
import * as Util from "./Util";
import {QRCode} from "antd";
class WeChatLoginPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
qrCode: null,
loading: false,
status: "loading",
ticket: null,
};
this.pollingTimer = null;
@@ -57,20 +58,20 @@ class WeChatLoginPanel extends React.Component {
const {application} = this.props;
const wechatProviderItem = application?.providers?.find(p => p.provider?.type === "WeChat");
if (wechatProviderItem) {
this.setState({loading: true, qrCode: null, ticket: null});
this.setState({status: "loading", qrCode: null, ticket: null});
AuthBackend.getWechatQRCode(`${wechatProviderItem.provider.owner}/${wechatProviderItem.provider.name}`).then(res => {
if (res.status === "ok" && res.data) {
this.setState({qrCode: res.data, loading: false, ticket: res.data2});
this.setState({qrCode: res.data, status: "active", ticket: res.data2});
this.clearPolling();
this.pollingTimer = setInterval(() => {
Util.getEvent(application, wechatProviderItem.provider, res.data2, "signup");
}, 1000);
} else {
this.setState({qrCode: null, loading: false, ticket: null});
this.setState({qrCode: null, status: "expired", ticket: null});
this.clearPolling();
}
}).catch(() => {
this.setState({qrCode: null, loading: false, ticket: null});
this.setState({qrCode: null, status: "expired", ticket: null});
this.clearPolling();
});
}
@@ -78,26 +79,20 @@ class WeChatLoginPanel extends React.Component {
render() {
const {application, loginWidth = 320} = this.props;
const {loading, qrCode} = this.state;
const {status, qrCode} = this.state;
return (
<div style={{width: loginWidth, margin: "0 auto", textAlign: "center", marginTop: 16}}>
{application.signinItems?.filter(item => item.name === "Logo").map(signinItem => this.props.renderFormItem(application, signinItem))}
{this.props.renderMethodChoiceBox()}
{application.signinItems?.filter(item => item.name === "Languages").map(signinItem => this.props.renderFormItem(application, signinItem))}
{loading ? (
<div style={{marginTop: 16}}>
<span>{i18next.t("login:Loading")}</span>
<div style={{marginTop: 2}}>
<QRCode style={{margin: "auto", marginTop: "20px", marginBottom: "20px"}} bordered={false} status={status} value={qrCode ?? " "} size={230} />
<div style={{marginTop: 8}}>
<a onClick={e => {e.preventDefault(); this.fetchQrCode();}}>
{i18next.t("login:Refresh")}
</a>
</div>
) : qrCode ? (
<div style={{marginTop: 2}}>
<img src={`data:image/png;base64,${qrCode}`} alt="WeChat QR code" style={{width: 250, height: 250}} />
<div style={{marginTop: 8}}>
<a onClick={e => {e.preventDefault(); this.fetchQrCode();}}>
{i18next.t("login:Refresh")}
</a>
</div>
</div>
) : null}
</div>
</div>
);
}