Compare commits

...

5 Commits

Author SHA1 Message Date
xiao-kong-long
2dc755f529 fix: add more cert algorithms like ES256 and PS256 (#2793) 2024-03-10 18:39:41 +08:00
Yang Luo
0dd474d5fc feat: fix public profile page shows blank page bug 2024-03-10 14:12:24 +08:00
Yang Luo
6998451e97 fix: support roles and permissions in /userinfo API 2024-03-10 12:34:56 +08:00
Yang Luo
9175e5b664 Fix bug in GetMaskedEmail() 2024-03-10 11:49:55 +08:00
DacongDA
dbc6b0dc45 feat: fix issue that forget password page fails to redirect back to signin page (#2792) 2024-03-10 09:55:44 +08:00
10 changed files with 231 additions and 15 deletions

View File

@@ -459,7 +459,12 @@ func (c *ApiController) GetUserinfo() {
scope, aud := c.GetSessionOidc()
host := c.Ctx.Request.Host
userInfo := object.GetUserInfo(user, scope, aud, host)
userInfo, err := object.GetUserInfo(user, scope, aud, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = userInfo
c.ServeJSON()

View File

@@ -529,11 +529,12 @@ func GetMaskedApplication(application *Application, userId string) *Application
application.OrganizationObj.PasswordSalt = "***"
application.OrganizationObj.InitScore = -1
application.OrganizationObj.EnableSoftDeletion = false
application.OrganizationObj.IsProfilePublic = false
if !isOrgUser {
application.OrganizationObj.MfaItems = nil
application.OrganizationObj.AccountItems = nil
if !application.OrganizationObj.IsProfilePublic {
application.OrganizationObj.AccountItems = nil
}
}
}

View File

@@ -16,6 +16,7 @@ package object
import (
"fmt"
"strings"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
@@ -206,7 +207,17 @@ func (p *Cert) GetId() string {
func (p *Cert) populateContent() error {
if p.Certificate == "" || p.PrivateKey == "" {
certificate, privateKey, err := generateRsaKeys(p.BitSize, p.ExpireInYears, p.Name, p.Owner)
var err error
var certificate, privateKey string
if strings.HasPrefix(p.CryptoAlgorithm, "RS") {
certificate, privateKey, err = generateRsaKeys(p.BitSize, util.ParseInt(p.CryptoAlgorithm[2:]), p.ExpireInYears, p.Name, p.Owner)
} else if strings.HasPrefix(p.CryptoAlgorithm, "ES") {
certificate, privateKey, err = generateEsKeys(p.BitSize, util.ParseInt(p.CryptoAlgorithm[2:]), p.ExpireInYears, p.Name, p.Owner)
} else if strings.HasPrefix(p.CryptoAlgorithm, "PS") {
certificate, privateKey, err = generateRsaPssKeys(p.BitSize, util.ParseInt(p.CryptoAlgorithm[2:]), p.ExpireInYears, p.Name, p.Owner)
} else {
err = fmt.Errorf("Crypto algorithm %s is not found", p.CryptoAlgorithm)
}
if err != nil {
return err
}

View File

@@ -15,16 +15,19 @@
package object
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"time"
)
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string, error) {
func generateRsaKeys(bitSize int, algorithmType int, expireInYears int, commonName string, organization string) (string, string, error) {
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
@@ -55,6 +58,132 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
BasicConstraintsValid: true,
}
switch algorithmType {
case 256:
tml.SignatureAlgorithm = x509.SHA256WithRSA
case 384:
tml.SignatureAlgorithm = x509.SHA384WithRSA
case 512:
tml.SignatureAlgorithm = x509.SHA512WithRSA
default:
return "", "", fmt.Errorf("unsupported algorithm type")
}
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
if err != nil {
return "", "", err
}
// Generate a pem block with the certificate
certPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert,
})
return string(certPem), string(privateKeyPem), nil
}
func generateEsKeys(bitSize int, algorithmType int, expireInYears int, commonName string, organization string) (string, string, error) {
var curve elliptic.Curve
switch algorithmType {
case 256:
curve = elliptic.P256()
case 384:
curve = elliptic.P384()
case 512:
curve = elliptic.P521() // ES512(P521,SHA512)
default:
return "", "", fmt.Errorf("unsupported algorithm type")
}
// Generate ECDSA key pair.
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return "", "", err
}
// Encode private key to PEM format.
privateKeyBytes, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return "", "", err
}
privateKeyPem := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: privateKeyBytes,
})
// Generate certificate template.
template := x509.Certificate{
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(expireInYears, 0, 0),
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{organization},
},
BasicConstraintsValid: true,
}
// Generate certificate.
certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
return "", "", err
}
// Encode certificate to PEM format.
certPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
return string(certPem), string(privateKeyPem), nil
}
func generateRsaPssKeys(bitSize int, algorithmType int, expireInYears int, commonName string, organization string) (string, string, error) {
// Generate RSA key.
key, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
return "", "", err
}
// Encode private key to PKCS#8 ASN.1 PEM.
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return "", "", err
}
privateKeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PSS PRIVATE KEY",
Bytes: privateKeyBytes,
},
)
tml := x509.Certificate{
// you can add any attr that you need
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(expireInYears, 0, 0),
// you have to generate a different serial number each execution
SerialNumber: big.NewInt(123456),
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{organization},
},
BasicConstraintsValid: true,
}
// Set the signature algorithm based on the hash function
switch algorithmType {
case 256:
tml.SignatureAlgorithm = x509.SHA256WithRSAPSS
case 384:
tml.SignatureAlgorithm = x509.SHA384WithRSAPSS
case 512:
tml.SignatureAlgorithm = x509.SHA512WithRSAPSS
default:
return "", "", fmt.Errorf("unsupported algorithm type")
}
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
if err != nil {
return "", "", err

View File

@@ -23,7 +23,35 @@ import (
func TestGenerateRsaKeys(t *testing.T) {
fileId := "token_jwt_key"
certificate, privateKey, err := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
certificate, privateKey, err := generateRsaKeys(4096, 512, 20, "Casdoor Cert", "Casdoor Organization")
if err != nil {
panic(err)
}
// Write certificate (aka certificate) to file.
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
// Write private key to file.
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))
}
func TestGenerateEsKeys(t *testing.T) {
fileId := "token_jwt_key"
certificate, privateKey, err := generateEsKeys(4096, 256, 20, "Casdoor Cert", "Casdoor Organization")
if err != nil {
panic(err)
}
// Write certificate (aka certificate) to file.
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
// Write private key to file.
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))
}
func TestGenerateRsaPssKeys(t *testing.T) {
fileId := "token_jwt_key"
certificate, privateKey, err := generateRsaPssKeys(4096, 256, 20, "Casdoor Cert", "Casdoor Organization")
if err != nil {
panic(err)
}

View File

@@ -216,6 +216,8 @@ type Userinfo struct {
Address string `json:"address,omitempty"`
Phone string `json:"phone,omitempty"`
Groups []string `json:"groups,omitempty"`
Roles []string `json:"roles,omitempty"`
Permissions []string `json:"permissions,omitempty"`
}
type ManagedAccount struct {
@@ -914,7 +916,7 @@ func DeleteUser(user *User) (bool, error) {
return affected != 0, nil
}
func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
_, originBackend := getOriginFromHost(host)
resp := Userinfo{
@@ -922,24 +924,44 @@ func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
Iss: originBackend,
Aud: aud,
}
if strings.Contains(scope, "profile") {
resp.Name = user.Name
resp.DisplayName = user.DisplayName
resp.Avatar = user.Avatar
resp.Groups = user.Groups
err := ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
resp.Roles = []string{}
for _, role := range user.Roles {
resp.Roles = append(resp.Roles, role.Name)
}
resp.Permissions = []string{}
for _, permission := range user.Permissions {
resp.Permissions = append(resp.Permissions, permission.Name)
}
}
if strings.Contains(scope, "email") {
resp.Email = user.Email
// resp.EmailVerified = user.EmailVerified
resp.EmailVerified = true
}
if strings.Contains(scope, "address") {
resp.Address = user.Location
}
if strings.Contains(scope, "phone") {
resp.Phone = user.Phone
}
return &resp
return &resp, nil
}
func LinkUserAccount(user *User, field string, value string) (bool, error) {

View File

@@ -264,6 +264,10 @@ func GetMaskedEmail(email string) string {
return ""
}
if !strings.Contains(email, "@") {
return maskString(email)
}
tokens := strings.Split(email, "@")
username := maskString(tokens[0])
domain := tokens[1]

View File

@@ -171,9 +171,13 @@ class CertEditPage extends React.Component {
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
this.updateCertField("cryptoAlgorithm", value);
if (value === "RS256") {
if (value === "RS256" || value === "PS256") {
this.updateCertField("bitSize", 2048);
} else if (value === "HS256" || value === "ES256") {
} else if (value === "RS384" || value === "PS384") {
this.updateCertField("bitSize", 2048);
} else if (value === "RS512" || value === "PS512") {
this.updateCertField("bitSize", 2048);
} else if (value === "ES256") {
this.updateCertField("bitSize", 256);
} else if (value === "ES384") {
this.updateCertField("bitSize", 384);
@@ -188,10 +192,14 @@ class CertEditPage extends React.Component {
{
[
{id: "RS256", name: "RS256 (RSA + SHA256)"},
{id: "HS256", name: "HS256 (HMAC + SHA256)"},
{id: "RS384", name: "RS384 (RSA + SHA384)"},
{id: "RS512", name: "RS512 (RSA + SHA512)"},
{id: "ES256", name: "ES256 (ECDSA using P-256 + SHA256)"},
{id: "ES384", name: "ES384 (ECDSA using P-384 + SHA256)"},
{id: "ES521", name: "ES521 (ECDSA using P-521 + SHA256)"},
{id: "ES384", name: "ES384 (ECDSA using P-384 + SHA384)"},
{id: "ES512", name: "ES512 (ECDSA using P-521 + SHA512)"},
{id: "PS256", name: "PS256 (RSASSA-PSS using SHA256 and MGF1 with SHA256)"},
{id: "PS384", name: "PS384 (RSASSA-PSS using SHA384 and MGF1 with SHA384)"},
{id: "PS512", name: "PS512 (RSASSA-PSS using SHA512 and MGF1 with SHA512)"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>

View File

@@ -201,6 +201,10 @@ class UserEditPage extends React.Component {
}
updateUserField(key, value) {
if (this.props.account === null) {
return;
}
value = this.parseUserField(key, value);
const user = this.state.user;
@@ -989,7 +993,11 @@ class UserEditPage extends React.Component {
<div style={{verticalAlign: "middle", marginBottom: 10}}>{`(${i18next.t("general:empty")})`}</div>
</Col>
}
<CropperDivModal disabled={disabled} tag={tag} setTitle={set} buttonText={`${title}...`} title={title} user={this.state.user} organization={this.getUserOrganization()} />
{
(this.props.account === null) ? null : (
<CropperDivModal disabled={disabled} tag={tag} setTitle={set} buttonText={`${title}...`} title={title} user={this.state.user} organization={this.getUserOrganization()} />
)
}
</Col>
);
}

View File

@@ -156,7 +156,7 @@ class ForgetPage extends React.Component {
if (res.status === "ok") {
const linkInStorage = sessionStorage.getItem("signinUrl");
if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLinkSoft(linkInStorage);
Setting.goToLinkSoft(this, linkInStorage);
} else {
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
}