feat: integrate Storage config into providers (#198)

Signed-off-by: Kininaru <shiftregister233@outlook.com>
This commit is contained in:
Kininaru 2021-07-26 11:39:49 +08:00 committed by GitHub
parent 1c01a34814
commit 3d493b8d8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 224 additions and 160 deletions

View File

@ -144,18 +144,3 @@ Now, Casdoor is running on port 8000. You can access Casdoor pages directly in y
export const GithubRepo = "https://github.com/casbin/casdoor" //your github repository
```
- OSS conf
We use an OSS to store and provide user avatars. You must modify the file `conf/oss.conf` to tell the backend your OSS info. For OSS providers, we support Aliyun(`[aliyun]`), awss3(`[s3]`) now.
```
[provider]
accessId = id
accessKey = key
bucket = bucket
endpoint = endpoint
```
Please fill out this conf correctly, or the avatar server won't work!

View File

@ -1,6 +0,0 @@
[provider]
endpoint = endpoint
accessId = id
accessKey = key
domain = domain
bucket = bucket

View File

@ -237,6 +237,8 @@ func (c *ApiController) UploadAvatar() {
var resp Response
user := object.GetUser(userId)
application := object.GetApplicationByUser(user)
provider := application.GetStorageProvider()
avatarBase64 := c.Ctx.Request.Form.Get("avatarfile")
index := strings.Index(avatarBase64, ",")
@ -248,14 +250,14 @@ func (c *ApiController) UploadAvatar() {
}
dist, _ := base64.StdEncoding.DecodeString(avatarBase64[index+1:])
msg := object.UploadAvatar(user.GetId(), dist)
msg := object.UploadAvatar(provider, user.GetId(), dist)
if msg != "" {
resp = Response{Status: "error", Msg: msg}
c.Data["json"] = resp
c.ServeJSON()
return
}
user.Avatar = fmt.Sprintf("%s%s.png?time=%s", object.GetAvatarPath(), user.GetId(), util.GetCurrentUnixTime())
user.Avatar = fmt.Sprintf("%s%s.png?time=%s", object.GetAvatarPath(provider), user.GetId(), util.GetCurrentUnixTime())
object.UpdateUser(user.GetId(), user)
resp = Response{Status: "ok", Msg: ""}
c.Data["json"] = resp

View File

@ -264,7 +264,7 @@ func (c *ApiController) Login() {
//}
//if len(object.GetMemberAvatar(userId)) == 0 {
// avatar := UploadAvatarToOSS(res.Avatar, userId)
// avatar := UploadAvatarToStorage(res.Avatar, userId)
// object.LinkMemberAccount(userId, "avatar", avatar)
//}
@ -357,7 +357,7 @@ func (c *ApiController) Login() {
resp = &Response{Status: "error", Msg: "Failed to link user account", Data: isLinked}
}
//if len(object.GetMemberAvatar(userId)) == 0 {
// avatar := UploadAvatarToOSS(tempUserAccount.AvatarUrl, userId)
// avatar := UploadAvatarToStorage(tempUserAccount.AvatarUrl, userId)
// object.LinkUserAccount(userId, "avatar", avatar)
//}
}

View File

@ -42,6 +42,10 @@ func (application *Application) GetSmsProvider() *Provider {
return application.getProviderByCategory("SMS")
}
func (application *Application) GetStorageProvider() *Provider {
return application.getProviderByCategory("Storage")
}
func (application *Application) getSignupItem(itemName string) *SignupItem {
for _, signupItem := range application.SignupItems {
if signupItem.Name == itemName {

View File

@ -1,130 +0,0 @@
// Copyright 2021 The casbin 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.
package object
import (
"bytes"
"fmt"
awss3 "github.com/aws/aws-sdk-go/service/s3"
"github.com/qor/oss"
"github.com/qor/oss/aliyun"
//"github.com/qor/oss/qiniu"
"github.com/qor/oss/s3"
"gopkg.in/ini.v1"
)
var storage oss.StorageInterface
var domain string
func AliyunInit(section *ini.Section) string {
endpoint := section.Key("endpoint").String()
accessId := section.Key("accessId").String()
accessKey := section.Key("accessKey").String()
domain = section.Key("domain").String()
bucket := section.Key("bucket").String()
if accessId == "" || accessKey == "" || bucket == "" || endpoint == "" {
return "Config oss.conf wrong"
}
storage = aliyun.New(&aliyun.Config{
AccessID: accessId,
AccessKey: accessKey,
Bucket: bucket,
Endpoint: endpoint,
})
return ""
}
//func QiniuInit(section *ini.Section) string {
// endpoint := section.Key("endpoint").String()
// accessId := section.Key("accessId").String()
// accessKey := section.Key("accessKey").String()
// domain = section.Key("domain").String()
// bucket := section.Key("bucket").String()
// region := section.Key("region").String()
// if accessId == "" || accessKey == "" || bucket == "" || endpoint == "" || region == "" {
// return "Config oss.conf wrong"
// }
// storage = qiniu.New(&qiniu.Config{
// AccessID: accessId,
// AccessKey: accessKey,
// Bucket: bucket,
// Region: region,
// Endpoint: endpoint,
// })
// return ""
//}
func Awss3Init(section *ini.Section) string {
endpoint := section.Key("endpoint").String()
accessId := section.Key("accessId").String()
accessKey := section.Key("accessKey").String()
domain = section.Key("domain").String()
bucket := section.Key("bucket").String()
region := section.Key("region").String()
if accessId == "" || accessKey == "" || bucket == "" || endpoint == "" || region == "" {
return "Config oss.conf wrong"
}
storage = s3.New(&s3.Config{
AccessID: accessId,
AccessKey: accessKey,
Region: region,
Bucket: bucket,
Endpoint: endpoint,
ACL: awss3.BucketCannedACLPublicRead,
})
return ""
}
func InitOssClient() {
if storage != nil {
return
}
ossConf, err := ini.Load("./conf/oss.conf")
if err != nil {
panic(err)
return
}
aliyunSection, _ := ossConf.GetSection("aliyun")
qiniuSection, _ := ossConf.GetSection("qiniu")
awss3Section, _ := ossConf.GetSection("s3")
if aliyunSection != nil {
AliyunInit(aliyunSection)
} else if qiniuSection != nil {
//QiniuInit(qiniuSection)
} else {
Awss3Init(awss3Section)
}
}
func UploadAvatar(username string, avatar []byte) string {
if storage == nil {
InitOssClient()
if storage == nil {
return "oss error"
}
}
path := fmt.Sprintf("/casdoor/avatar/%s.png", username)
_, err := storage.Put(path, bytes.NewReader(avatar))
if err != nil {
panic(err)
}
return ""
}
func GetAvatarPath() string {
return fmt.Sprintf("https://%s/casdoor/avatar/", domain)
}

View File

@ -15,6 +15,8 @@
package object
import (
"fmt"
"github.com/casbin/casdoor/util"
"xorm.io/core"
)
@ -40,6 +42,10 @@ type Provider struct {
TemplateCode string `xorm:"varchar(100)" json:"templateCode"`
AppId string `xorm:"varchar(100)" json:"appId"`
Endpoint string `xorm:"varchar(100)" json:"endpoint"`
Domain string `xorm:"varchar(100)" json:"domain"`
Bucket string `xorm:"varchar(100)" json:"bucket"`
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
}
@ -134,3 +140,7 @@ func DeleteProvider(provider *Provider) bool {
return affected != 0
}
func (p *Provider) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}

136
object/storage.go Normal file
View File

@ -0,0 +1,136 @@
// Copyright 2021 The casbin 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.
package object
import (
"bytes"
"fmt"
awss3 "github.com/aws/aws-sdk-go/service/s3"
"github.com/casbin/casdoor/util"
"github.com/qor/oss"
"github.com/qor/oss/aliyun"
//"github.com/qor/oss/qiniu"
"github.com/qor/oss/s3"
)
func getAliyunClient(provider *Provider) oss.StorageInterface {
if util.IsStrsEmpty(
provider.Endpoint,
provider.ClientId,
provider.ClientSecret,
provider.Bucket) {
return nil
}
ret := aliyun.New(&aliyun.Config{
AccessID: provider.ClientId,
AccessKey: provider.ClientSecret,
Bucket: provider.Bucket,
Endpoint: provider.Endpoint,
})
if len(provider.Domain) == 0 {
provider.Domain = ret.GetEndpoint()
UpdateProvider(provider.GetId(), provider)
}
return ret
}
func getQiniuClient(provider *Provider) oss.StorageInterface {
fmt.Println("Casdoor does not support Qiniu now.")
return nil
// endpoint := section.Key("endpoint").String()
// accessId := section.Key("accessId").String()
// accessKey := section.Key("accessKey").String()
// domain = section.Key("domain").String()
// bucket := section.Key("bucket").String()
// region := section.Key("region").String()
// if accessId == "" || accessKey == "" || bucket == "" || endpoint == "" || region == "" {
// return "Config oss.conf wrong"
// }
// storage = qiniu.New(&qiniu.Config{
// AccessID: accessId,
// AccessKey: accessKey,
// Bucket: bucket,
// Region: region,
// Endpoint: endpoint,
// })
// return ""
}
func getAwss3Client(provider *Provider) oss.StorageInterface {
if util.IsStrsEmpty(
provider.Endpoint,
provider.ClientId,
provider.ClientSecret,
provider.Bucket,
provider.RegionId) {
return nil
}
ret := s3.New(&s3.Config{
AccessID: provider.ClientId,
AccessKey: provider.ClientSecret,
Region: provider.RegionId,
Bucket: provider.Bucket,
Endpoint: provider.Endpoint,
ACL: awss3.BucketCannedACLPublicRead,
})
if len(provider.Domain) == 0 {
provider.Domain = ret.GetEndpoint()
UpdateProvider(provider.GetId(), provider)
}
return ret
}
func getStorageClient(provider *Provider) oss.StorageInterface {
if provider == nil || provider.Category != "Storage" {
return nil
}
switch provider.Type {
case "Aliyun":
return getAliyunClient(provider)
case "Qiniu":
return getQiniuClient(provider)
case "AWSS3":
return getAwss3Client(provider)
}
return nil
}
func UploadAvatar(provider *Provider, username string, avatar []byte) string {
if provider == nil {
return "invalid Storage provider"
}
storage := getStorageClient(provider)
if storage == nil {
return "oss provider not exists"
}
path := fmt.Sprintf("/casdoor/avatar/%s.png", username)
_, err := storage.Put(path, bytes.NewReader(avatar))
if err != nil {
panic(err)
}
return ""
}
func GetAvatarPath(provider *Provider) string {
return fmt.Sprintf("https://%s/casdoor/avatar/", provider.Domain)
}

View File

@ -57,13 +57,12 @@ func GetMd5Hash(text string) string {
}
func IsStrsEmpty(strs ...string) bool {
r := false
for _, str := range strs {
if len(str) == 0 {
r = true
return true
}
}
return r
return false
}
func GetMaxLenStr(strs ...string) string {

View File

@ -83,6 +83,14 @@ class ProviderEditPage extends React.Component {
{id: 'Default', name: 'Default'},
]
);
} else if (provider.category === "Storage") {
return (
[
{id: 'Default', name: 'Default'},
{id: 'Aliyun', name: 'Aliyun'},
{id: 'AWSS3', name: 'AWS s3'},
]
);
} else {
return (
[
@ -143,6 +151,7 @@ class ProviderEditPage extends React.Component {
{id: 'OAuth', name: 'OAuth'},
{id: 'Email', name: 'Email'},
{id: 'SMS', name: 'SMS'},
{id: 'Storage', name: 'Storage'},
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
}
</Select>
@ -180,6 +189,52 @@ class ProviderEditPage extends React.Component {
}} />
</Col>
</Row>
{this.state.provider.category === "Storage" ? (
<div>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Endpoint - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.endpoint} onChange={e => {
this.updateProviderField('endpoint', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.bucket} onChange={e => {
this.updateProviderField('bucket', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.domain} onChange={e => {
this.updateProviderField('domain', e.target.value);
}} />
</Col>
</Row>
{this.state.provider.type === "AWSS3" ? (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Region"), i18next.t("provider:Region - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.regionId} onChange={e => {
this.updateProviderField('regionId', e.target.value);
}} />
</Col>
</Row>
) : null}
</div>
) : null}
{
this.state.provider.category === "Email" ? (
<React.Fragment>

View File

@ -84,7 +84,8 @@ class OAuthWidget extends React.Component {
getUserProperty(user, providerType, propertyName) {
const key = `oauth_${providerType}_${propertyName}`;
return user.properties[key]
if (user.properties === null) return "";
return user.properties[key];
}
unlinkUser(providerType) {

View File

@ -184,7 +184,15 @@
"Template Code - Tooltip": "Unique string-style identifier",
"Provider URL": "Provider URL",
"Provider URL - Tooltip": "Unique string-style identifier",
"Edit Provider": "Edit Provider"
"Edit Provider": "Edit Provider",
"Endpoint": "Endpoint",
"Endpoint - Tooltip": "Storage bucket endpoint",
"Bucket": "Bucket",
"Bucket - Tooltip": "Storage bucket name",
"Domain": "Domain",
"Domain - Tooltip": "Storage endpoint custom domain",
"Region": "Region",
"Region - Tooltip": "Storage region"
},
"user":
{