diff --git a/docs/opendata对外接口文档.docx b/docs/opendata对外接口文档.docx index 15619ffec..f37a31f2c 100755 Binary files a/docs/opendata对外接口文档.docx and b/docs/opendata对外接口文档.docx differ diff --git a/docs/云脑用户登录流程图.png b/docs/云脑用户登录流程图.png new file mode 100755 index 000000000..c05ca75b8 Binary files /dev/null and b/docs/云脑用户登录流程图.png differ diff --git a/docs/开源社区与云脑平台对接方案.docx b/docs/开源社区与云脑平台对接方案.docx new file mode 100755 index 000000000..8db5c651a Binary files /dev/null and b/docs/开源社区与云脑平台对接方案.docx differ diff --git a/docs/用户登录流程图.png b/docs/用户登录流程图.png new file mode 100755 index 000000000..e69c03734 Binary files /dev/null and b/docs/用户登录流程图.png differ diff --git a/models/attachment.go b/models/attachment.go index be0507d37..3d4e4d136 100755 --- a/models/attachment.go +++ b/models/attachment.go @@ -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) +} + diff --git a/models/login_source.go b/models/login_source.go old mode 100644 new mode 100755 index 535044623..7153606a9 --- a/models/login_source.go +++ b/models/login_source.go @@ -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 +} diff --git a/models/user.go b/models/user.go old mode 100644 new mode 100755 index 8875840db..29e1a5d9f --- a/models/user.go +++ b/models/user.go @@ -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 diff --git a/modules/auth/cloudbrain/cloudbrain.go b/modules/auth/cloudbrain/cloudbrain.go new file mode 100755 index 000000000..4020faa5f --- /dev/null +++ b/modules/auth/cloudbrain/cloudbrain.go @@ -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 +} diff --git a/modules/cloudbrain/cloudbrain.go b/modules/cloudbrain/cloudbrain.go old mode 100644 new mode 100755 diff --git a/modules/setting/setting.go b/modules/setting/setting.go index a1b72594e..af902cb79 100755 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -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 { diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 3e32702aa..2e56fbb06 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -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=名称 diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index c2bbc5d46..3881ff733 100755 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -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 } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 5d6e015f1..8d722cfe2 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -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() { diff --git a/routers/user/auth.go b/routers/user/auth.go old mode 100644 new mode 100755 index e1a885480..421598347 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -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") diff --git a/templates/repo/datasets/index.tmpl b/templates/repo/datasets/index.tmpl index 1c6a2a2b2..6e629bdc7 100755 --- a/templates/repo/datasets/index.tmpl +++ b/templates/repo/datasets/index.tmpl @@ -3,6 +3,11 @@ {{template "repo/header" .}}