zerotier.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. package service
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "io/ioutil"
  7. "math/rand"
  8. "net/http"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "unicode"
  13. "github.com/IceWhaleTech/CasaOS/pkg/config"
  14. command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command"
  15. httper2 "github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
  16. "github.com/IceWhaleTech/CasaOS/pkg/zerotier"
  17. "github.com/PuerkitoBio/goquery"
  18. "github.com/tidwall/gjson"
  19. )
  20. type ZeroTierService interface {
  21. GetToken(username, pwd string) string
  22. ZeroTierRegister(email, lastName, firstName, password string) string
  23. ZeroTierNetworkList(token string) (interface{}, []string)
  24. ZeroTierJoinNetwork(networkId string)
  25. ZeroTierLeaveNetwork(networkId string)
  26. ZeroTierGetInfo(token, id string) (interface{}, []string)
  27. ZeroTierGetStatus(token string) interface{}
  28. EditNetwork(token string, data string, id string) interface{}
  29. CreateNetwork(token string) interface{}
  30. MemberList(token string, id string) interface{}
  31. EditNetworkMember(token string, data string, id, mId string) interface{}
  32. DeleteMember(token string, id, mId string) interface{}
  33. DeleteNetwork(token, id string) interface{}
  34. GetJoinNetworks() string
  35. NetworkIdFilter(letter rune) bool
  36. }
  37. type zerotierStruct struct {
  38. }
  39. var client http.Client
  40. func (c *zerotierStruct) ZeroTierJoinNetwork(networkId string) {
  41. command2.OnlyExec(`zerotier-cli join ` + networkId)
  42. }
  43. func (c *zerotierStruct) ZeroTierLeaveNetwork(networkId string) {
  44. command2.OnlyExec(`zerotier-cli leave ` + networkId)
  45. }
  46. //登录并获取token
  47. func (c *zerotierStruct) GetToken(username, pwd string) string {
  48. if len(config.ZeroTierInfo.Token) > 0 {
  49. return config.ZeroTierInfo.Token
  50. } else {
  51. return LoginGetToken(username, pwd)
  52. }
  53. }
  54. func (c *zerotierStruct) ZeroTierRegister(email, lastName, firstName, password string) string {
  55. 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"
  56. action, cookies, _ := ZeroTierGet(url, nil, 4)
  57. var buff bytes.Buffer
  58. buff.WriteString("email=")
  59. buff.WriteString(email)
  60. buff.WriteString("&password=")
  61. buff.WriteString(password)
  62. buff.WriteString("&password-confirm=")
  63. buff.WriteString(password)
  64. buff.WriteString("&user.attributes.marketingOptIn=true")
  65. buff.WriteString("&firstName")
  66. buff.WriteString(firstName)
  67. buff.WriteString("&lastName")
  68. buff.WriteString(lastName)
  69. action, errInfo, _ := ZeroTierPost(buff, action, cookies, false)
  70. if len(errInfo) > 0 {
  71. return errInfo
  72. }
  73. action, _, _ = ZeroTierGet(action, cookies, 5)
  74. return ""
  75. }
  76. //固定请求head
  77. func GetHead() map[string]string {
  78. var head = make(map[string]string, 4)
  79. head["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
  80. head["Accept-Language"] = "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"
  81. head["Connection"] = "keep-alive"
  82. 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"
  83. return head
  84. }
  85. //登录并获取token,会出现账号密码错误,和邮箱未验证情况,目前未出现其他情况
  86. func LoginGetToken(username, pwd string) string {
  87. //拿到登录的action
  88. 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"
  89. action, cookies, _ := ZeroTierGet(loginUrl, nil, 1)
  90. if len(action) == 0 {
  91. //没有拿到action,页面结构变了
  92. return ""
  93. }
  94. //登录
  95. var str bytes.Buffer
  96. str.WriteString("username=")
  97. str.WriteString(username)
  98. str.WriteString("&password=")
  99. str.WriteString(pwd)
  100. str.WriteString("&credentialId=&login=Log+In")
  101. url, logingErrInfo, _ := ZeroTierPost(str, action, cookies, true)
  102. action, cookies, isLoginOk := ZeroTierGet(url, cookies, 2)
  103. if isLoginOk {
  104. //登录成功,可以继续调用api
  105. randomTokenUrl := "https://my.zerotier.com/api/randomToken"
  106. json, _, _ := ZeroTierGet(randomTokenUrl, cookies, 3)
  107. //获取一个随机token
  108. token := gjson.Get(json, "token")
  109. userInfoUrl := "https://my.zerotier.com/api/status"
  110. json, _, _ = ZeroTierGet(userInfoUrl, cookies, 3)
  111. //拿到用户id
  112. userId := gjson.Get(json, "user.id")
  113. //设置新token
  114. addTokenUrl := "https://my.zerotier.com/api/user/" + userId.String() + "/token"
  115. data := make(map[string]string)
  116. rand.Seed(time.Now().UnixNano())
  117. data["tokenName"] = "oasis-token-" + strconv.Itoa(rand.Intn(1000))
  118. data["token"] = token.String()
  119. head := make(map[string]string)
  120. head["Content-Type"] = "application/json"
  121. _, statusCode := httper2.ZeroTierPost(addTokenUrl, data, head, cookies)
  122. if statusCode == http.StatusOK {
  123. config.Cfg.Section("zerotier").Key("Token").SetValue(token.String())
  124. config.Cfg.SaveTo("conf/conf.ini")
  125. config.ZeroTierInfo.Token = token.String()
  126. }
  127. } else {
  128. //登录错误信息
  129. if len(logingErrInfo) > 0 {
  130. return logingErrInfo
  131. } else {
  132. //验证邮箱
  133. action, _, _ = ZeroTierGet(url, cookies, 5)
  134. return "You need to verify your email address to activate your account."
  135. }
  136. }
  137. return ""
  138. }
  139. // t 1:获取action,2:登录成功后拿session(可能需要验证有了或登录失败) 3:随机生成token 4:注册页面拿action 5:注册成功后拿验证邮箱的地址
  140. func ZeroTierGet(url string, cookies []*http.Cookie, t uint8) (action string, c []*http.Cookie, isExistSession bool) {
  141. isExistSession = false
  142. action = ""
  143. c = []*http.Cookie{}
  144. request, _ := http.NewRequest(http.MethodGet, url, nil)
  145. for k, v := range GetHead() {
  146. request.Header.Add(k, v)
  147. }
  148. for _, cookie := range cookies {
  149. request.AddCookie(cookie)
  150. }
  151. resp, err := client.Do(request)
  152. if err != nil {
  153. return
  154. }
  155. defer resp.Body.Close()
  156. c = resp.Cookies()
  157. if t == 1 {
  158. doc, err := goquery.NewDocumentFromReader(resp.Body)
  159. if err != nil {
  160. return
  161. }
  162. action, _ = doc.Find("#kc-form-login").Attr("action")
  163. return
  164. } else if t == 2 {
  165. for _, cookie := range resp.Cookies() {
  166. if cookie.Name == "pgx-session" {
  167. isExistSession = true
  168. break
  169. }
  170. }
  171. //判断是否登录成功,如果需要验证邮箱,则返回验证邮箱的地址。
  172. if resp.StatusCode == http.StatusFound && len(resp.Header.Get("Location")) > 0 {
  173. action = resp.Header.Get("Location")
  174. }
  175. return
  176. } else if t == 3 {
  177. //返回获取到的字符串
  178. byteArr, _ := ioutil.ReadAll(resp.Body)
  179. action = string(byteArr)
  180. } else if t == 4 {
  181. doc, err := goquery.NewDocumentFromReader(resp.Body)
  182. if err != nil {
  183. return
  184. }
  185. action, _ = doc.Find("#kc-register-form").Attr("action")
  186. return
  187. } else if t == 5 {
  188. doc, _ := goquery.NewDocumentFromReader(resp.Body)
  189. fmt.Println(doc.Html())
  190. action, _ = doc.Find("#kc-info-wrapper a").Attr("href")
  191. return
  192. }
  193. return
  194. }
  195. //模拟提交表单
  196. func ZeroTierPost(str bytes.Buffer, action string, cookies []*http.Cookie, isLogin bool) (url, errInfo string, err error) {
  197. req, err := http.NewRequest(http.MethodPost, action, strings.NewReader(str.String()))
  198. if err != nil {
  199. return "", "", errors.New("newrequest error")
  200. }
  201. for k, v := range GetHead() {
  202. req.Header.Set(k, v)
  203. }
  204. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  205. for _, cookie := range cookies {
  206. req.AddCookie(cookie)
  207. }
  208. res, err := client.Do(req)
  209. defer res.Body.Close()
  210. if err != nil {
  211. return "", "", errors.New("request error")
  212. }
  213. if !isLogin {
  214. //注册成功
  215. if res.StatusCode == http.StatusFound && len(res.Header.Get("Location")) > 0 {
  216. return res.Header.Get("Location"), "", nil
  217. } else {
  218. register, _ := goquery.NewDocumentFromReader(res.Body)
  219. firstErr := strings.TrimSpace(register.Find("#input-error-firstname").Text())
  220. lastErr := strings.TrimSpace(register.Find("#input-error-lastname").Text())
  221. emailErr := strings.TrimSpace(register.Find("#input-error-email").Text())
  222. pwdErr := strings.TrimSpace(register.Find("#input-error-password").Text())
  223. var errD strings.Builder
  224. if len(firstErr) > 0 {
  225. errD.WriteString(firstErr + ",")
  226. }
  227. if len(lastErr) > 0 {
  228. errD.WriteString(lastErr + ",")
  229. }
  230. if len(emailErr) > 0 {
  231. errD.WriteString(emailErr + ",")
  232. }
  233. if len(pwdErr) > 0 {
  234. errD.WriteString(pwdErr + ",")
  235. }
  236. return "", errD.String(), nil
  237. }
  238. } else {
  239. if res.StatusCode == http.StatusFound && len(res.Header.Get("Location")) > 0 {
  240. return res.Header.Get("Location"), "", nil
  241. }
  242. doc, err := goquery.NewDocumentFromReader(res.Body)
  243. if err != nil {
  244. return "", "", errors.New("request error")
  245. }
  246. errDesc := doc.Find("#input-error").Text()
  247. if len(errDesc) > 0 {
  248. return "", strings.TrimSpace(errDesc), nil
  249. }
  250. }
  251. return "", "", nil
  252. }
  253. //获取zerotile网络列表和本地用户已加入的网络
  254. func (c *zerotierStruct) ZeroTierNetworkList(token string) (interface{}, []string) {
  255. url := "https://my.zerotier.com/api/network"
  256. return zerotier.GetData(url, token), command2.ExecResultStrArray(`zerotier-cli listnetworks | awk 'NR>1 {print $3} {line=$0}'`)
  257. }
  258. // get network info
  259. func (c *zerotierStruct) ZeroTierGetInfo(token, id string) (interface{}, []string) {
  260. url := "https://my.zerotier.com/api/network/" + id
  261. info := zerotier.GetData(url, token)
  262. return info, command2.ExecResultStrArray(`zerotier-cli listnetworks | awk 'NR>1 {print $3} {line=$0}'`)
  263. }
  264. //get status
  265. func (c *zerotierStruct) ZeroTierGetStatus(token string) interface{} {
  266. url := "https://my.zerotier.com/api/v1/status"
  267. info := zerotier.GetData(url, token)
  268. return info
  269. }
  270. func (c *zerotierStruct) EditNetwork(token string, data string, id string) interface{} {
  271. url := "https://my.zerotier.com/api/v1/network/" + id
  272. info := zerotier.PostData(url, token, data)
  273. return info
  274. }
  275. func (c *zerotierStruct) EditNetworkMember(token string, data string, id, mId string) interface{} {
  276. url := "https://my.zerotier.com/api/v1/network/" + id + "/member/" + mId
  277. info := zerotier.PostData(url, token, data)
  278. return info
  279. }
  280. func (c *zerotierStruct) MemberList(token string, id string) interface{} {
  281. url := "https://my.zerotier.com/api/v1/network/" + id + "/member"
  282. info := zerotier.GetData(url, token)
  283. return info
  284. }
  285. func (c *zerotierStruct) DeleteMember(token string, id, mId string) interface{} {
  286. url := "https://my.zerotier.com/api/v1/network/" + id + "/member/" + mId
  287. info := zerotier.DeleteMember(url, token)
  288. return info
  289. }
  290. func (c *zerotierStruct) DeleteNetwork(token, id string) interface{} {
  291. url := "https://my.zerotier.com/api/v1/network/" + id
  292. info := zerotier.DeleteMember(url, token)
  293. return info
  294. }
  295. func (c *zerotierStruct) CreateNetwork(token string) interface{} {
  296. url := "https://my.zerotier.com/api/v1/network"
  297. info := zerotier.PostData(url, token, "{}")
  298. return info
  299. }
  300. func (c *zerotierStruct) GetJoinNetworks() string {
  301. json := command2.ExecResultStr("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetLocalJoinNetworks")
  302. return json
  303. }
  304. func (c *zerotierStruct) NetworkIdFilter(letter rune) bool {
  305. if unicode.IsNumber(letter) || unicode.IsLetter(letter) {
  306. return true
  307. } else {
  308. return false
  309. }
  310. }
  311. func NewZeroTierService() ZeroTierService {
  312. //初始化client
  313. client = http.Client{Timeout: 30 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error {
  314. return http.ErrUseLastResponse //禁止重定向
  315. },
  316. }
  317. return &zerotierStruct{}
  318. }