CasaOS/service/zerotier.go
2022-01-26 18:50:34 +08:00

353 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"strings"
"time"
"unicode"
"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"
)
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
NetworkIdFilter(letter rune) bool
}
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:获取action2登录成功后拿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, cookies []*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 cookies {
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 (c *zerotierStruct) NetworkIdFilter(letter rune) bool {
if unicode.IsNumber(letter) || unicode.IsLetter(letter) {
return true
} else {
return false
}
}
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{}
}