Reviewed-by: stardust <denglf@pcl.ac.cn>tags/v0.1.8
@@ -6,6 +6,7 @@ package models | |||
import ( | |||
"bytes" | |||
"code.gitea.io/gitea/modules/log" | |||
"fmt" | |||
"io" | |||
"path" | |||
@@ -353,5 +354,20 @@ func GetAllPublicAttachments() ([]*Attachment, error) { | |||
func getAllPublicAttachments(e Engine) ([]*Attachment, error) { | |||
attachments := make([]*Attachment, 0, 10) | |||
return attachments, e.Where("is_private = true ").Find(&attachments) | |||
return attachments, e.Where("is_private = false and decompress_state = ?", DecompressStateDone).Find(&attachments) | |||
} | |||
func GetPrivateAttachments(username string) ([]*Attachment, error) { | |||
user, err := getUserByName(x, username) | |||
if err != nil { | |||
log.Error("getUserByName(%s) failed:%v", username, err) | |||
return nil, err | |||
} | |||
return getPrivateAttachments(x, user.ID) | |||
} | |||
func getPrivateAttachments(e Engine, userID int64) ([]*Attachment, error) { | |||
attachments := make([]*Attachment, 0, 10) | |||
return attachments, e.Where("uploader_id = ? and decompress_state = ?", userID, DecompressStateDone).Find(&attachments) | |||
} | |||
@@ -6,6 +6,7 @@ | |||
package models | |||
import ( | |||
"code.gitea.io/gitea/modules/auth/cloudbrain" | |||
"crypto/tls" | |||
"encoding/json" | |||
"errors" | |||
@@ -39,6 +40,7 @@ const ( | |||
LoginDLDAP // 5 | |||
LoginOAuth2 // 6 | |||
LoginSSPI // 7 | |||
LoginCloudBrain // 8 | |||
) | |||
// LoginNames contains the name of LoginType values. | |||
@@ -49,6 +51,7 @@ var LoginNames = map[LoginType]string{ | |||
LoginPAM: "PAM", | |||
LoginOAuth2: "OAuth2", | |||
LoginSSPI: "SPNEGO with SSPI", | |||
LoginCloudBrain: "Cloud Brain", | |||
} | |||
// SecurityProtocolNames contains the name of SecurityProtocol values. | |||
@@ -199,6 +202,8 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { | |||
source.Cfg = new(OAuth2Config) | |||
case LoginSSPI: | |||
source.Cfg = new(SSPIConfig) | |||
case LoginCloudBrain: | |||
source.Cfg = new(CloudBrainConfig) | |||
default: | |||
panic("unrecognized login source type: " + com.ToStr(*val)) | |||
} | |||
@@ -714,6 +719,8 @@ func ExternalUserLogin(user *User, login, password string, source *LoginSource) | |||
user, err = LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig)) | |||
case LoginPAM: | |||
user, err = LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig)) | |||
case LoginCloudBrain: | |||
user, err = LoginViaCloudBrain(user, login, password, source) | |||
default: | |||
return nil, ErrUnsupportedLoginType | |||
} | |||
@@ -817,3 +824,46 @@ func UserSignIn(username, password string) (*User, error) { | |||
return nil, ErrUserNotExist{user.ID, user.Name, 0} | |||
} | |||
func LoginViaCloudBrain(user *User, login, password string, source *LoginSource) (*User, error) { | |||
token, err := cloudbrain.UserValidate(login, password) | |||
if err != nil { | |||
log.Error("UserValidate(%s) failed: %v", login, err) | |||
return nil, err | |||
} | |||
if user != nil { | |||
user.Token = token | |||
return user, UpdateUserCols(user, "token") | |||
} | |||
cloudBrainUser, err := cloudbrain.GetUserInfo(login, token) | |||
if err != nil { | |||
log.Error("GetUserInfo(%s) failed: %v", login, err) | |||
return nil, err | |||
} | |||
if len(cloudBrainUser.Email) == 0 { | |||
cloudBrainUser.Email = fmt.Sprintf("%s@cloudbrain", login) | |||
} | |||
user = &User{ | |||
LowerName: strings.ToLower(login), | |||
Name: login, | |||
Email: cloudBrainUser.Email, | |||
LoginType: source.Type, | |||
LoginSource: source.ID, | |||
LoginName: login, | |||
IsActive: true, | |||
Token: token, | |||
} | |||
err = CreateUser(user) | |||
if err != nil { | |||
log.Error("CreateUser(%s) failed: %v", login, err) | |||
return nil, err | |||
} | |||
return user, nil | |||
} |
@@ -165,6 +165,9 @@ type User struct { | |||
// Preferences | |||
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"` | |||
Theme string `xorm:"NOT NULL DEFAULT ''"` | |||
//CloudBrain | |||
Token string `xorm:"VARCHAR(1024)"` | |||
} | |||
// SearchOrganizationsOptions options to filter organizations | |||
@@ -0,0 +1,136 @@ | |||
package cloudbrain | |||
import ( | |||
"bytes" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"encoding/json" | |||
"errors" | |||
"io/ioutil" | |||
"net/http" | |||
"strings" | |||
) | |||
const ( | |||
UrlToken = "/rest-server/api/v1/token/" | |||
UrlGetUserInfo = "/rest-server/api/v1/user/" | |||
TokenTypeBear = "Bearer " | |||
SuccessCode = "S000" | |||
) | |||
type RespAuth struct { | |||
AccessToken string `json:"access_token"` | |||
RefreshToken string `json:"refresh_token"` | |||
TokenType string `json:"token_type"` | |||
ExpiresIn int `json:"expires_in"` | |||
Error string `json:"error"` | |||
ErrorDescription string `json:"error_description"` | |||
} | |||
type RespToken struct { | |||
Code string `json:"code"` | |||
Message string `json:"msg"` | |||
Payload PayloadToken `json:"payload"` | |||
} | |||
type PayloadToken struct { | |||
Username string `json:"username"` | |||
Token string `json:"token"` | |||
IsAdmin bool `json:"admin"` | |||
} | |||
type RespUserInfo struct { | |||
Code string `json:"code"` | |||
Message string `json:"msg"` | |||
Payload PayloadUserInfo `json:"payload"` | |||
} | |||
type PayloadUserInfo struct { | |||
UserInfo StUserInfo `json:"userInfo"` | |||
} | |||
type StUserInfo struct { | |||
Email string `json:"email"` | |||
} | |||
type CloudBrainUser struct { | |||
UserName string `json:"username"` | |||
Email string `json:"email"` | |||
} | |||
func UserValidate(username string, password string) (string, error) { | |||
values := map[string]string{"username": username, "password": password} | |||
jsonValue, _ := json.Marshal(values) | |||
resp, err := http.Post(setting.RestServerHost + UrlToken, | |||
"application/json", | |||
bytes.NewBuffer(jsonValue)) | |||
if err != nil { | |||
log.Error("req user center failed:" + err.Error()) | |||
return "", err | |||
} | |||
defer resp.Body.Close() | |||
body,err := ioutil.ReadAll(resp.Body) | |||
if err != nil { | |||
log.Error("read resp body failed:" + err.Error()) | |||
return "", err | |||
} | |||
var res RespToken | |||
err = json.Unmarshal(body, &res) | |||
if err != nil { | |||
log.Error("unmarshal res failed:" + err.Error()) | |||
return "", err | |||
} | |||
if res.Code != SuccessCode { | |||
log.Error("req rest-server for token failed:", res.Message) | |||
return "", errors.New(res.Message) | |||
} | |||
return res.Payload.Token, nil | |||
} | |||
func GetUserInfo(username string, token string) (*CloudBrainUser, error) { | |||
user := &CloudBrainUser{} | |||
client := &http.Client{} | |||
reqHttp,err := http.NewRequest("GET", setting.RestServerHost + UrlGetUserInfo + username, strings.NewReader("")) | |||
if err != nil { | |||
log.Error("new req failed:", err.Error()) | |||
return nil, err | |||
} | |||
reqHttp.Header.Set("Authorization", TokenTypeBear + token) | |||
resp,err := client.Do(reqHttp) | |||
if err != nil { | |||
log.Error("req rest-server failed:", err.Error()) | |||
return nil, err | |||
} | |||
defer resp.Body.Close() | |||
body,err := ioutil.ReadAll(resp.Body) | |||
if err != nil { | |||
log.Error("read resp body failed:", err.Error()) | |||
return nil, err | |||
} | |||
var res RespUserInfo | |||
err = json.Unmarshal(body, &res) | |||
if err != nil { | |||
log.Error("unmarshal resp failed:", err.Error()) | |||
return nil, err | |||
} | |||
if res.Code != SuccessCode { | |||
log.Error("get userInfo failed:", err.Error()) | |||
return nil, err | |||
} | |||
user.Email = res.Payload.UserInfo.Email | |||
return user, nil | |||
} |
@@ -434,6 +434,10 @@ var ( | |||
//cloudbrain config | |||
CBAuthUser string | |||
CBAuthPassword string | |||
ClientID string | |||
ClientSecret string | |||
UserCeterHost string | |||
RestServerHost string | |||
) | |||
// DateLang transforms standard language locale name to corresponding value in datetime plugin. | |||
@@ -1105,6 +1109,10 @@ func NewContext() { | |||
sec = Cfg.Section("cloudbrain") | |||
CBAuthUser = sec.Key("USER").MustString("cW4cMtH24eoWPE7X") | |||
CBAuthPassword = sec.Key("PWD").MustString("4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC") | |||
ClientID = sec.Key("CLIENT_ID").MustString("3Z377wcplxeE2qpycpjv") | |||
ClientSecret = sec.Key("CLIENT_SECRET").MustString("J5ykfVl2kcxW0H9cawSL") | |||
UserCeterHost = sec.Key("USER_CENTER_HOST").MustString("http://192.168.202.73:31441") | |||
RestServerHost = sec.Key("REST_SERVER_HOST").MustString("http://192.168.202.73") | |||
} | |||
func loadInternalToken(sec *ini.Section) string { | |||
@@ -192,6 +192,7 @@ no_reply_address_helper=具有隐藏电子邮件地址的用户的域名。例 | |||
[home] | |||
uname_holder=登录名或电子邮箱地址 | |||
uname_holder_cloud_brain=云脑登录名 | |||
password_holder=密码 | |||
switch_dashboard_context=切换控制面板用户 | |||
my_repos=项目列表 | |||
@@ -267,6 +268,7 @@ twofa_passcode_incorrect=你的验证码不正确。如果你丢失了你的设 | |||
twofa_scratch_token_incorrect=你的验证口令不正确。 | |||
login_userpass=登录 | |||
login_openid=OpenID | |||
login_cloudbrain=云脑用户登录 | |||
oauth_signup_tab=注册帐号 | |||
oauth_signup_title=添加电子邮件和密码 (用于帐号恢复) | |||
oauth_signup_submit=完成账号 | |||
@@ -615,6 +617,7 @@ email_notifications.disable=停用邮件通知 | |||
email_notifications.submit=邮件通知设置 | |||
[dataset] | |||
alert=如果要发起云脑任务,请上传zip格式的数据集 | |||
dataset=数据集 | |||
dataset_setting=数据集设置 | |||
title=名称 | |||
@@ -5,13 +5,6 @@ | |||
package repo | |||
import ( | |||
contexExt "context" | |||
"encoding/json" | |||
"fmt" | |||
"net/http" | |||
"strconv" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/log" | |||
@@ -20,6 +13,12 @@ import ( | |||
"code.gitea.io/gitea/modules/storage" | |||
"code.gitea.io/gitea/modules/upload" | |||
"code.gitea.io/gitea/modules/worker" | |||
contexExt "context" | |||
"encoding/json" | |||
"fmt" | |||
"net/http" | |||
"strconv" | |||
"strings" | |||
gouuid "github.com/satori/go.uuid" | |||
) | |||
@@ -30,9 +29,12 @@ const ( | |||
DecompressFailed = "1" | |||
) | |||
type PublicDataset struct { | |||
type CloudBrainDataset struct { | |||
UUID string `json:"id"` | |||
Name string `json:"name"` | |||
Path string `json:"path"` | |||
Path string `json:"place"` | |||
UserName string `json:"provider"` | |||
CreateTime string `json:"created_at"` | |||
} | |||
func RenderAttachmentSettings(ctx *context.Context) { | |||
@@ -627,22 +629,55 @@ func QueryAllPublicDataset(ctx *context.Context){ | |||
if err != nil { | |||
ctx.JSON(200, map[string]string{ | |||
"result_code": "-1", | |||
"error_msg": err.Error(), | |||
"data": "", | |||
}) | |||
return | |||
} | |||
queryDatasets(ctx, "admin", attachs) | |||
} | |||
func QueryPrivateDataset(ctx *context.Context){ | |||
username := ctx.Params(":username") | |||
attachs, err := models.GetPrivateAttachments(username) | |||
if err != nil { | |||
ctx.JSON(200, map[string]string{ | |||
"result_code": "-1", | |||
"error_msg": err.Error(), | |||
"data": "", | |||
}) | |||
return | |||
} | |||
var publicDatasets []PublicDataset | |||
queryDatasets(ctx, username, attachs) | |||
} | |||
func queryDatasets(ctx *context.Context, username string, attachs []*models.Attachment) { | |||
var datasets []CloudBrainDataset | |||
for _, attch := range attachs { | |||
publicDatasets = append(publicDatasets, PublicDataset{attch.Name, | |||
models.AttachmentRelativePath(attch.UUID)}) | |||
has,err := storage.Attachments.HasObject(models.AttachmentRelativePath(attch.UUID)) | |||
if err != nil || !has { | |||
continue | |||
} | |||
datasets = append(datasets, CloudBrainDataset{attch.UUID, | |||
attch.Name, | |||
setting.Attachment.Minio.RealPath + | |||
setting.Attachment.Minio.Bucket + "/" + | |||
setting.Attachment.Minio.BasePath + | |||
models.AttachmentRelativePath(attch.UUID) + | |||
attch.UUID, | |||
username, | |||
attch.CreatedUnix.Format("2006-01-02 03:04:05")}) | |||
} | |||
data,err := json.Marshal(publicDatasets) | |||
data,err := json.Marshal(datasets) | |||
if err != nil { | |||
log.Error("json.Marshal failed:", err.Error()) | |||
ctx.JSON(200, map[string]string{ | |||
"result_code": "-1", | |||
"error_msg": err.Error(), | |||
"data": "", | |||
}) | |||
return | |||
@@ -650,6 +685,8 @@ func QueryAllPublicDataset(ctx *context.Context){ | |||
ctx.JSON(200, map[string]string{ | |||
"result_code": "0", | |||
"error_msg": "", | |||
"data": string(data), | |||
}) | |||
return | |||
} |
@@ -305,6 +305,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
// ***** START: User ***** | |||
m.Group("/user", func() { | |||
m.Get("/login", user.SignIn) | |||
m.Get("/login/cloud_brain", user.SignInCloudBrain) | |||
m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) | |||
m.Group("", func() { | |||
m.Combo("/login/openid"). | |||
@@ -534,8 +535,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Post("/decompress_done_notify", repo.UpdateAttachmentDecompressState) | |||
}) | |||
m.Group("/attachments/public", func() { | |||
m.Get("/query", repo.QueryAllPublicDataset) | |||
m.Group("/attachments", func() { | |||
m.Get("/public/query", repo.QueryAllPublicDataset) | |||
m.Get("/private/:username", repo.QueryPrivateDataset) | |||
}, reqBasicAuth) | |||
m.Group("/:username", func() { | |||
@@ -36,6 +36,8 @@ const ( | |||
tplMustChangePassword = "user/auth/change_passwd" | |||
// tplSignIn template for sign in page | |||
tplSignIn base.TplName = "user/auth/signin" | |||
// tplSignIn template for sign in page | |||
tplSignInCloudBrain base.TplName = "user/auth/signin_cloud_brain" | |||
// tplSignUp template path for sign up page | |||
tplSignUp base.TplName = "user/auth/signup" | |||
// TplActivate template path for activate user | |||
@@ -143,10 +145,28 @@ func SignIn(ctx *context.Context) { | |||
ctx.Data["PageIsSignIn"] = true | |||
ctx.Data["PageIsLogin"] = true | |||
ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() | |||
ctx.Data["EnableCloudBrain"] = true | |||
ctx.HTML(200, tplSignIn) | |||
} | |||
// SignInCloudBrain render sign in page | |||
func SignInCloudBrain(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
// Check auto-login. | |||
if checkAutoLogin(ctx) { | |||
return | |||
} | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | |||
ctx.Data["PageIsSignIn"] = true | |||
ctx.Data["PageIsCloudBrainLogin"] = true | |||
ctx.Data["EnableCloudBrain"] = true | |||
ctx.HTML(200, tplSignInCloudBrain) | |||
} | |||
// SignInPost response for sign in request | |||
func SignInPost(ctx *context.Context, form auth.SignInForm) { | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
@@ -3,6 +3,11 @@ | |||
{{template "repo/header" .}} | |||
<form class="ui container" action="{{.Link}}" method="post"> | |||
<input name="id" value="{{.dataset.ID}}" type="hidden" /> | |||
<!-- | |||
<span class="alert" style="font-size:20px;color:red"> | |||
<strong>{{.i18n.Tr "dataset.alert"}}</strong> | |||
</span> | |||
--> | |||
<div id="datasetId" datasetId="{{.dataset.ID}}"> | |||
{{.CsrfTokenHtml}} | |||
{{template "base/alert" .}} | |||
@@ -0,0 +1,10 @@ | |||
{{template "base/head" .}} | |||
<div class="user signin"> | |||
{{template "user/auth/signin_navbar" .}} | |||
<div class="ui container"> | |||
<div class="ui raised very padded text container segment"> | |||
{{template "user/auth/signin_cloudbrain" .}} | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -0,0 +1,55 @@ | |||
<style> | |||
.full.height{background-color: #F9F9F9;} | |||
.ui.left:not(.action){ float:none;} | |||
.ui.left{ float:none;} | |||
.ui.secondary.pointing.menu{ border-bottom:none;} | |||
</style> | |||
{{template "base/alert" .}} | |||
<div class="ui centered grid"> | |||
<div class="sixteen wide mobile ten wide tablet ten wide computer column"> | |||
<div class="ui bottom aligned two column grid"> | |||
<div class="column"> | |||
<h2 class="ui header"> | |||
{{.i18n.Tr "auth.login_userpass"}} | |||
</h2> | |||
</div> | |||
</div> | |||
<div class="ui grid"> | |||
<div class="column"> | |||
<form class="ui form" action="{{.SignInLink}}" method="post"> | |||
{{.CsrfTokenHtml}} | |||
<div class="field"> | |||
<div class="ui left icon input {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}"> | |||
<i class="user icon"></i> | |||
<input id="user_name" name="user_name" value="{{.user_name}}" placeholder="{{.i18n.Tr "home.uname_holder_cloud_brain"}}" autofocus required> | |||
</div> | |||
</div> | |||
<div class="field"> | |||
<div class="ui left icon input {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}"> | |||
<i class="lock icon"></i> | |||
<input id="password" name="password" type="password" value="{{.password}}" placeholder="{{.i18n.Tr "password"}}" autocomplete="off" required> | |||
</div> | |||
</div> | |||
<div class="two fields inline"> | |||
<div class="field"> | |||
<div class="ui checkbox"> | |||
<label>{{.i18n.Tr "auth.remember_me"}}</label> | |||
<input name="remember" type="checkbox"> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui hidden divider"></div> | |||
<div class="center aligned field"> | |||
<button class="fluid large ui blue button"> | |||
{{.i18n.Tr "sign_in"}} | |||
</button> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
</div> |
@@ -1,8 +1,11 @@ | |||
{{if or .EnableOpenIDSignIn .EnableSSPI}} | |||
{{if or .EnableOpenIDSignIn .EnableSSPI .EnableCloudBrain}} | |||
<div class="ui secondary pointing tabular top attached borderless menu new-menu navbar"> | |||
<a class="{{if .PageIsLogin}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login"> | |||
{{.i18n.Tr "auth.login_userpass"}} | |||
</a> | |||
<a class="{{if .PageIsCloudBrainLogin}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/cloud_brain"> | |||
{{.i18n.Tr "auth.login_cloudbrain"}} | |||
</a> | |||
{{if .EnableOpenIDSignIn}} | |||
<a class="{{if .PageIsLoginOpenID}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/openid"> | |||
<i class="fa fa-openid"></i> | |||