package service import ( "bytes" "errors" "fmt" "github.com/IceWhaleTech/CasaOS/pkg/config" command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command" httper2 "github.com/IceWhaleTech/CasaOS/pkg/utils/httper" "github.com/IceWhaleTech/CasaOS/pkg/zerotier" "github.com/PuerkitoBio/goquery" "github.com/tidwall/gjson" "io/ioutil" "math/rand" "net/http" "strconv" "strings" "time" ) type ZeroTierService interface { GetToken(username, pwd string) string ZeroTierRegister(email, lastName, firstName, password string) string ZeroTierNetworkList(token string) (interface{}, []string) ZeroTierJoinNetwork(networkId string) ZeroTierLeaveNetwork(networkId string) ZeroTierGetInfo(token, id string) (interface{}, []string) ZeroTierGetStatus(token string) interface{} EditNetwork(token string, data string, id string) interface{} CreateNetwork(token string) interface{} MemberList(token string, id string) interface{} EditNetworkMember(token string, data string, id, mId string) interface{} DeleteMember(token string, id, mId string) interface{} DeleteNetwork(token, id string) interface{} GetJoinNetworks() string } type zerotierstruct struct { } var client http.Client func (c *zerotierstruct) ZeroTierJoinNetwork(networkId string) { command2.OnlyExec(`zerotier-cli join ` + networkId) } func (c *zerotierstruct) ZeroTierLeaveNetwork(networkId string) { command2.OnlyExec(`zerotier-cli leave ` + networkId) } //登录并获取token func (c *zerotierstruct) GetToken(username, pwd string) string { if len(config.ZeroTierInfo.Token) > 0 { return config.ZeroTierInfo.Token } else { return LoginGetToken(username, pwd) } } func (c *zerotierstruct) ZeroTierRegister(email, lastName, firstName, password string) string { url := "https://accounts.zerotier.com/auth/realms/zerotier/protocol/openid-connect/registrations?client_id=zt-central&redirect_uri=https%3A%2F%2Fmy.zerotier.com%2Fapi%2F_auth%2Foidc%2Fcallback&response_type=code&scope=openid+profile+email+offline_access&state=state" action, cookies, _ := ZeroTierGet(url, nil, 4) var buff bytes.Buffer buff.WriteString("email=") buff.WriteString(email) buff.WriteString("&password=") buff.WriteString(password) buff.WriteString("&password-confirm=") buff.WriteString(password) buff.WriteString("&user.attributes.marketingOptIn=true") buff.WriteString("&firstName") buff.WriteString(firstName) buff.WriteString("&lastName") buff.WriteString(lastName) action, errInfo, _ := ZeroTierPost(buff, action, cookies, false) if len(errInfo) > 0 { return errInfo } action, _, _ = ZeroTierGet(action, cookies, 5) return "" } //固定请求head func GetHead() map[string]string { var head = make(map[string]string, 4) head["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8" head["Accept-Language"] = "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3" head["Connection"] = "keep-alive" head["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" return head } //登录并获取token,会出现账号密码错误,和邮箱未验证情况,目前未出现其他情况 func LoginGetToken(username, pwd string) string { //拿到登录的action var loginUrl = "https://accounts.zerotier.com/auth/realms/zerotier/protocol/openid-connect/auth?client_id=zt-central&redirect_uri=https%3A%2F%2Fmy.zerotier.com%2Fapi%2F_auth%2Foidc%2Fcallback&response_type=code&scope=openid+profile+email+offline_access&state=states" action, cookies, _ := ZeroTierGet(loginUrl, nil, 1) if len(action) == 0 { //没有拿到action,页面结构变了 return "" } //登录 var str bytes.Buffer str.WriteString("username=") str.WriteString(username) str.WriteString("&password=") str.WriteString(pwd) str.WriteString("&credentialId=&login=Log+In") url, logingErrInfo, _ := ZeroTierPost(str, action, cookies, true) action, cookies, isLoginOk := ZeroTierGet(url, cookies, 2) if isLoginOk { //登录成功,可以继续调用api randomTokenUrl := "https://my.zerotier.com/api/randomToken" json, _, _ := ZeroTierGet(randomTokenUrl, cookies, 3) //获取一个随机token token := gjson.Get(json, "token") userInfoUrl := "https://my.zerotier.com/api/status" json, _, _ = ZeroTierGet(userInfoUrl, cookies, 3) //拿到用户id userId := gjson.Get(json, "user.id") //设置新token addTokenUrl := "https://my.zerotier.com/api/user/" + userId.String() + "/token" data := make(map[string]string) rand.Seed(time.Now().UnixNano()) data["tokenName"] = "oasis-token-" + strconv.Itoa(rand.Intn(1000)) data["token"] = token.String() head := make(map[string]string) head["Content-Type"] = "application/json" _, statusCode := httper2.ZeroTierPost(addTokenUrl, data, head, cookies) if statusCode == http.StatusOK { config.Cfg.Section("zerotier").Key("Token").SetValue(token.String()) config.Cfg.SaveTo("conf/conf.ini") config.ZeroTierInfo.Token = token.String() } } else { //登录错误信息 if len(logingErrInfo) > 0 { return logingErrInfo } else { //验证邮箱 action, _, _ = ZeroTierGet(url, cookies, 5) return "You need to verify your email address to activate your account." } } return "" } // t 1:获取action,2:登录成功后拿session(可能需要验证有了或登录失败) 3:随机生成token 4:注册页面拿action 5:注册成功后拿验证邮箱的地址 func ZeroTierGet(url string, cookies []*http.Cookie, t uint8) (action string, c []*http.Cookie, isExistSession bool) { isExistSession = false action = "" c = []*http.Cookie{} request, _ := http.NewRequest(http.MethodGet, url, nil) for k, v := range GetHead() { request.Header.Add(k, v) } for _, cookie := range cookies { request.AddCookie(cookie) } resp, err := client.Do(request) if err != nil { return } defer resp.Body.Close() c = resp.Cookies() if t == 1 { doc, err := goquery.NewDocumentFromReader(resp.Body) if err != nil { return } action, _ = doc.Find("#kc-form-login").Attr("action") return } else if t == 2 { for _, cookie := range resp.Cookies() { if cookie.Name == "pgx-session" { isExistSession = true break } } //判断是否登录成功,如果需要验证邮箱,则返回验证邮箱的地址。 if resp.StatusCode == http.StatusFound && len(resp.Header.Get("Location")) > 0 { action = resp.Header.Get("Location") } return } else if t == 3 { //返回获取到的字符串 byteArr, _ := ioutil.ReadAll(resp.Body) action = string(byteArr) } else if t == 4 { doc, err := goquery.NewDocumentFromReader(resp.Body) if err != nil { return } action, _ = doc.Find("#kc-register-form").Attr("action") return } else if t == 5 { doc, _ := goquery.NewDocumentFromReader(resp.Body) fmt.Println(doc.Html()) action, _ = doc.Find("#kc-info-wrapper a").Attr("href") return } return } //模拟提交表单 func ZeroTierPost(str bytes.Buffer, action string, cookes []*http.Cookie, isLogin bool) (url, errInfo string, err error) { req, err := http.NewRequest(http.MethodPost, action, strings.NewReader(str.String())) if err != nil { return "", "", errors.New("newrequest error") } for k, v := range GetHead() { req.Header.Set(k, v) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") for _, cookie := range cookes { req.AddCookie(cookie) } res, err := client.Do(req) defer res.Body.Close() if err != nil { return "", "", errors.New("request error") } if !isLogin { //注册成功 if res.StatusCode == http.StatusFound && len(res.Header.Get("Location")) > 0 { return res.Header.Get("Location"), "", nil } else { register, _ := goquery.NewDocumentFromReader(res.Body) firstErr := strings.TrimSpace(register.Find("#input-error-firstname").Text()) lastErr := strings.TrimSpace(register.Find("#input-error-lastname").Text()) emailErr := strings.TrimSpace(register.Find("#input-error-email").Text()) pwdErr := strings.TrimSpace(register.Find("#input-error-password").Text()) var errD strings.Builder if len(firstErr) > 0 { errD.WriteString(firstErr + ",") } if len(lastErr) > 0 { errD.WriteString(lastErr + ",") } if len(emailErr) > 0 { errD.WriteString(emailErr + ",") } if len(pwdErr) > 0 { errD.WriteString(pwdErr + ",") } return "", errD.String(), nil } } else { if res.StatusCode == http.StatusFound && len(res.Header.Get("Location")) > 0 { return res.Header.Get("Location"), "", nil } doc, err := goquery.NewDocumentFromReader(res.Body) if err != nil { return "", "", errors.New("request error") } errDesc := doc.Find("#input-error").Text() if len(errDesc) > 0 { return "", strings.TrimSpace(errDesc), nil } } return "", "", nil } //获取zerotile网络列表和本地用户已加入的网络 func (c *zerotierstruct) ZeroTierNetworkList(token string) (interface{}, []string) { url := "https://my.zerotier.com/api/network" return zerotier.GetData(url, token), command2.ExecResultStrArray(`zerotier-cli listnetworks | awk 'NR>1 {print $3} {line=$0}'`) } // get network info func (c *zerotierstruct) ZeroTierGetInfo(token, id string) (interface{}, []string) { url := "https://my.zerotier.com/api/network/" + id info := zerotier.GetData(url, token) return info, command2.ExecResultStrArray(`zerotier-cli listnetworks | awk 'NR>1 {print $3} {line=$0}'`) } //get status func (c *zerotierstruct) ZeroTierGetStatus(token string) interface{} { url := "https://my.zerotier.com/api/v1/status" info := zerotier.GetData(url, token) return info } func (c *zerotierstruct) EditNetwork(token string, data string, id string) interface{} { url := "https://my.zerotier.com/api/v1/network/" + id info := zerotier.PostData(url, token, data) return info } func (c *zerotierstruct) EditNetworkMember(token string, data string, id, mId string) interface{} { url := "https://my.zerotier.com/api/v1/network/" + id + "/member/" + mId info := zerotier.PostData(url, token, data) return info } func (c *zerotierstruct) MemberList(token string, id string) interface{} { url := "https://my.zerotier.com/api/v1/network/" + id + "/member" info := zerotier.GetData(url, token) return info } func (c *zerotierstruct) DeleteMember(token string, id, mId string) interface{} { url := "https://my.zerotier.com/api/v1/network/" + id + "/member/" + mId info := zerotier.DeleteMember(url, token) return info } func (c *zerotierstruct) DeleteNetwork(token, id string) interface{} { url := "https://my.zerotier.com/api/v1/network/" + id info := zerotier.DeleteMember(url, token) return info } func (c *zerotierstruct) CreateNetwork(token string) interface{} { url := "https://my.zerotier.com/api/v1/network" info := zerotier.PostData(url, token, "{}") return info } func (c *zerotierstruct) GetJoinNetworks() string { json := command2.ExecResultStr("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetLocalJoinNetworks") return json } func NewZeroTierService() ZeroTierService { //初始化client client = http.Client{Timeout: 30 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse //禁止重定向 }, } return &zerotierstruct{} }