package security import ( "encoding/json" "fmt" "image/color" "io" "net/http" "strconv" "strings" "time" "ems.agt/features/security/service" sysConfigService "ems.agt/features/sys_config/service" "ems.agt/lib/core/account" "ems.agt/lib/core/cache" "ems.agt/lib/core/conf" "ems.agt/lib/core/constants/cachekey" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/vo/result" "ems.agt/lib/dborm" "ems.agt/lib/global" "ems.agt/lib/log" "ems.agt/lib/oauth" "ems.agt/lib/services" "ems.agt/restagent/config" "github.com/go-admin-team/go-admin-core/logger" "github.com/mojocn/base64Captcha" ) var ( UriOauthToken = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/token" UriOauthHandshake = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/handshake" CustomUriOauthToken = config.UriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/token" CustomUriOauthHandshake = config.UriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/handshake" // 系统登录 UriLogin = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/login" CustomUriLogin = config.UriPrefix + "/securityManagement/{apiVersion}/login" // 获取验证码 UriCaptchaImage = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/captchaImage" CustomUriCaptchaImage = config.UriPrefix + "/securityManagement/{apiVersion}/captchaImage" // 登录用户信息 UriUserInfo = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/getUserInfo" CustomUriUserInfo = config.UriPrefix + "/securityManagement/{apiVersion}/getUserInfo" // 登录用户路由信息 UriRouters = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/getRouters" CustomUriRouters = config.UriPrefix + "/securityManagement/{apiVersion}/getRouters" ) func LoginFromOMC(w http.ResponseWriter, r *http.Request) { log.Info("LoginFromOMC processing... ") body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小 if err != nil { log.Error("Failed to ReadAll:", err) services.ResponseNotFound404UriNotExist(w, r) return } // check media type(content type) only support "application/json" if !services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) { log.Debug("Invalid Content-Type") services.ResponseUnsupportedMediaType415(w) return } // // check extend uri, response 404 // if !IsValidOAuthUri(r) { // log.Debug("Uri is invalid") // services.ResponseNotFound404UriNotExist(w, r) // return // } // Error process .... // response 400-7 if !json.Valid([]byte(body)) { log.Error("Invalid Json Format") services.ResponseBadRequest400InvalidJson(w) return } var oAuthBody oauth.OAuthBody _ = json.Unmarshal(body, &oAuthBody) //转为json //log.Debug("body:", string(body), "oAuthBody:", oAuthBody) defer r.Body.Close() // response 400-5 if oauth.IsWrongOAuthInfo(oAuthBody) { log.Error("Wrong parameter value") services.ResponseBadRequest400WrongParamValue(w) return } /* if oauth.IsValidOAuthInfo(oAuthBody) { plist := config.GetPermissionFromConfig(oAuthBody.UserName, oAuthBody.GrantType) log.Debug("Permission list:", plist) token := globalSession.NewSession(w, r, plist) services.ResponseStatusOK200Login(w, token) } else { // response 400-4 log.Debug("Authentication failed, mismatch user or password") services.ResponseBadRequest400IncorrectLogin(w) } */ validUser, user, err := dborm.XormCheckLoginUser(oAuthBody.UserName, oAuthBody.Value, config.GetYamlConfig().Auth.Crypt) if !validUser || err != nil { // response 400-4 log.Error("Authentication failed, mismatch user or password") services.ResponseErrorWithJson(w, 400, err.Error()) return } token := oauth.GenRandToken("omc") // Generate new token to session ID sourceAddr := r.RemoteAddr[:strings.Index(r.RemoteAddr, ":")] affected, err := dborm.XormInsertSession(oAuthBody.UserName, sourceAddr, token, config.GetExpiresFromConfig(), config.GetYamlConfig().Auth.Session) if err != nil { log.Error("Failed to XormInsertSession:", err) if affected == -1 { services.ResponseForbidden403MultiLoginNotAllowed(w) } else { services.ResponseBadRequest400IncorrectLogin(w) } return } if user != nil { // 缓存用户信息 account.CacheLoginUser(user) // 角色权限集合,管理员拥有所有权限 userId := fmt.Sprint(user.Id) isAdmin := conf.IsAdmin(userId) roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin) services.ResponseStatusOK200LoginWhitRP(w, token, user, roles, perms) return } services.ResponseBadRequest400IncorrectLogin(w) } func LogoutFromOMC(w http.ResponseWriter, r *http.Request) { log.Info("LogoutFromOMC processing... ") token, err := services.CheckFrontValidRequest(w, r) if err != nil { log.Error("Request error:", err) return } // // check media type(content type) only support "application/json" // if services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) == false { // log.Error("Invalid Content-Type") // services.ResponseUnsupportedMediaType415(w) // return // } // // check extend uri, response 404 // if !services.IsValidOAuthUri(r) { // log.Error("Uri is invalid") // services.ResponseNotFound404UriNotExist(w, r) // return // } // // error processing ... // // 401-1 response // token, ret := oauth.IsCarriedToken(r) // if ret == false { // log.Error("AccessToken is not carried") // services.ResponseUnauthorized401AccessTokenNotCarried(w) // return // } se, err := dborm.XormLogoutUpdateSession(token) if err != nil { log.Error("Uri is invalid") services.ResponseNotFound404UriNotExist(w, r) return } // 清除缓存用户信息 account.ClearLoginUser(se.AccountId) services.ResponseStatusOK200Null(w) } func HandshakeFromOMC(w http.ResponseWriter, r *http.Request) { log.Info("HandshakeFromOMC processing... ") // check media type(content type) only support "application/json" if !services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) { log.Debug("Invalid Content-Type") services.ResponseUnsupportedMediaType415(w) return } // check extend uri, response 404 if !services.IsValidOAuthUri(r) { log.Error("Uri is invalid") services.ResponseNotFound404UriNotExist(w, r) return } // error processing ... // 401-1 response token, ret := oauth.IsCarriedToken(r) if !ret { log.Error("AccessToken is not carried") services.ResponseUnauthorized401AccessTokenNotCarried(w) return } _, err := dborm.XormUpdateSessionShakeTime(token) if err != nil { log.Error("Uri is invalid") services.ResponseNotFound404UriNotExist(w, r) return } services.ResponseStatusOK200Null(w) } // 系统登录 // // POST /login func LoginOMC(w http.ResponseWriter, r *http.Request) { log.Info("LoginOMC processing... ") var body struct { Username string `json:"username" binding:"required"` // Username 用户名 Password string `json:"password" binding:"required"` // Password 用户密码 Code string `json:"code"` // Code 验证码 UUID string `json:"uuid"` // UUID 验证码唯一标识 } err := ctx.ShouldBindJSON(r, &body) if err != nil { log.Error("Invalid Json Format") ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) return } // response 400-5 if body.Username == "" || body.Password == "" { log.Error("Wrong parameter value") ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) return } // 校验验证码 // 从数据库配置获取验证码开关 true开启,false关闭 captchaEnabledStr := sysConfigService.NewServiceSysConfig.SelectConfigValueByKey("sys.account.captchaEnabled") captchaEnabled, err := strconv.ParseBool(captchaEnabledStr) if err != nil { captchaEnabled = false } if captchaEnabled { if body.Code == "" || body.UUID == "" { log.Error("Authentication failed, mismatch captcha") ctx.JSON(w, 400, result.CodeMsg(400, "验证码信息错误")) return } verifyKey := cachekey.CAPTCHA_CODE_KEY + body.UUID captcha, ok := cache.GetLocalTTL(verifyKey) if captcha == nil || !ok { log.Error("Authentication failed, captcha emtry") ctx.JSON(w, 400, result.CodeMsg(400, "验证码已失效")) return } cache.DeleteLocalTTL(verifyKey) if captcha.(string) != body.Code { log.Error("Authentication failed, not match captcha") ctx.JSON(w, 400, result.CodeMsg(400, "验证码错误")) return } } validUser, user, err := dborm.XormCheckLoginUser(body.Username, body.Password, config.GetYamlConfig().Auth.Crypt) if !validUser || err != nil { // response 400-4 log.Error("Authentication failed, mismatch user or password") ctx.JSON(w, 400, result.CodeMsg(400, err.Error())) return } token := oauth.GenRandToken("omc") // Generate new token to session ID sourceAddr := r.RemoteAddr[:strings.Index(r.RemoteAddr, ":")] affected, err := dborm.XormInsertSession(body.Username, sourceAddr, token, config.GetExpiresFromConfig(), config.GetYamlConfig().Auth.Session) if err != nil { log.Error("Failed to XormInsertSession:", err) if affected == -1 { services.ResponseForbidden403MultiLoginNotAllowed(w) } else { services.ResponseBadRequest400IncorrectLogin(w) } return } if user != nil { // 缓存用户信息 account.CacheLoginUser(user) ctx.JSON(w, 200, result.OkData(map[string]any{ "accessToken": token, })) return } ctx.JSON(w, 200, result.Err(nil)) } // 获取验证码 // // GET /captchaImage func CaptchaImage(w http.ResponseWriter, r *http.Request) { configService := sysConfigService.NewServiceSysConfig // 从数据库配置获取验证码开关 true开启,false关闭 captchaEnabledStr := configService.SelectConfigValueByKey("sys.account.captchaEnabled") captchaEnabled, err := strconv.ParseBool(captchaEnabledStr) if err != nil { captchaEnabled = false } if !captchaEnabled { ctx.JSON(w, 200, result.Ok(map[string]any{ "captchaEnabled": captchaEnabled, })) return } // 生成唯一标识 verifyKey := "" data := map[string]any{ "captchaEnabled": captchaEnabled, "uuid": "", "img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", } // char 字符验证 driverCaptcha := &base64Captcha.DriverString{ //Height png height in pixel. Height: 40, // Width Captcha png width in pixel. Width: 120, //NoiseCount text noise count. NoiseCount: 4, //Length random string length. Length: 4, //Source is a unicode which is the rand string from. Source: "023456789abcdefghjkmnprstuvwxyz", //ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine . ShowLineOptions: base64Captcha.OptionShowHollowLine, //BgColor captcha image background color (optional) BgColor: &color.RGBA{ R: 250, G: 250, B: 250, A: 255, // 不透明 }, } // 验证码生成 id, question, answer := driverCaptcha.GenerateIdQuestionAnswer() // 验证码表达式解析输出 item, err := driverCaptcha.DrawCaptcha(question) if err != nil { logger.Infof("Generate Id Question Answer %s : %v", question, err) } else { data["uuid"] = id data["img"] = item.EncodeB64string() verifyKey = cachekey.CAPTCHA_CODE_KEY + id cache.SetLocalTTL(verifyKey, answer, 120*time.Second) } // 本地开发下返回验证码结果,方便接口调试 // text, ok := cache.GetLocalTTL(verifyKey) // if ok { // data["text"] = text.(string) // } ctx.JSON(w, 200, result.Ok(data)) } // 登录用户信息 func UserInfo(w http.ResponseWriter, r *http.Request) { loginUser, err := ctx.LoginUser(r) if err != nil { ctx.JSON(w, 200, result.OkData(err.Error())) } // 角色权限集合,管理员拥有所有权限 userId := fmt.Sprint(loginUser.UserID) isAdmin := conf.IsAdmin(userId) roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin) ctx.JSON(w, 200, result.OkData(map[string]any{ "user": loginUser.User, "roles": roles, "permissions": perms, })) } // 登录用户路由信息 func Routers(w http.ResponseWriter, r *http.Request) { userID := ctx.LoginUserToUserID(r) // 前端路由,管理员拥有所有 isAdmin := conf.IsAdmin(userID) buildMenus := service.NewServiceAccount.RouteMenus(userID, isAdmin) ctx.JSON(w, 200, result.OkData(buildMenus)) }