google.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package google
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "encoding/json"
  6. "errors"
  7. "strings"
  8. "net/url"
  9. "github.com/gravitl/netmaker/logic"
  10. "github.com/gravitl/netmaker/pro/idp"
  11. admindir "google.golang.org/api/admin/directory/v1"
  12. "google.golang.org/api/googleapi"
  13. "google.golang.org/api/impersonate"
  14. "google.golang.org/api/option"
  15. )
  16. type Client struct {
  17. service *admindir.Service
  18. }
  19. func NewGoogleWorkspaceClient(adminEmail, creds string) (*Client, error) {
  20. credsJson, err := base64.StdEncoding.DecodeString(creds)
  21. if err != nil {
  22. return nil, err
  23. }
  24. credsJsonMap := make(map[string]interface{})
  25. err = json.Unmarshal(credsJson, &credsJsonMap)
  26. if err != nil {
  27. return nil, err
  28. }
  29. var targetPrincipal string
  30. _, ok := credsJsonMap["client_email"]
  31. if !ok {
  32. return nil, errors.New("invalid service account credentials: missing client_email field")
  33. }
  34. targetPrincipal = credsJsonMap["client_email"].(string)
  35. source, err := impersonate.CredentialsTokenSource(
  36. context.TODO(),
  37. impersonate.CredentialsConfig{
  38. TargetPrincipal: targetPrincipal,
  39. Scopes: []string{
  40. admindir.AdminDirectoryUserReadonlyScope,
  41. admindir.AdminDirectoryGroupReadonlyScope,
  42. admindir.AdminDirectoryGroupMemberReadonlyScope,
  43. },
  44. Subject: adminEmail,
  45. },
  46. option.WithAuthCredentialsJSON(option.ServiceAccount, credsJson),
  47. )
  48. if err != nil {
  49. return nil, err
  50. }
  51. service, err := admindir.NewService(
  52. context.TODO(),
  53. option.WithTokenSource(source),
  54. )
  55. if err != nil {
  56. return nil, err
  57. }
  58. return &Client{
  59. service: service,
  60. }, nil
  61. }
  62. func NewGoogleWorkspaceClientFromSettings() (*Client, error) {
  63. settings := logic.GetServerSettings()
  64. return NewGoogleWorkspaceClient(settings.GoogleAdminEmail, settings.GoogleSACredsJson)
  65. }
  66. func (g *Client) Verify() error {
  67. _, err := g.service.Users.List().
  68. Customer("my_customer").
  69. MaxResults(1).
  70. Do()
  71. if err != nil {
  72. var gerr *googleapi.Error
  73. if errors.As(err, &gerr) {
  74. return errors.New(gerr.Message)
  75. }
  76. var uerr *url.Error
  77. if errors.As(err, &uerr) {
  78. errMsg := strings.TrimSpace(uerr.Err.Error())
  79. if strings.Contains(errMsg, "{") && strings.HasSuffix(errMsg, "}") {
  80. // probably contains response json.
  81. _, jsonBody, _ := strings.Cut(errMsg, "{")
  82. jsonBody = "{" + jsonBody
  83. var errResp errorResponse
  84. err := json.Unmarshal([]byte(jsonBody), &errResp)
  85. if err == nil && errResp.Error.Message != "" {
  86. return errors.New(errResp.Error.Message)
  87. }
  88. }
  89. }
  90. return err
  91. }
  92. _, err = g.service.Groups.List().
  93. Customer("my_customer").
  94. MaxResults(1).
  95. Do()
  96. if err != nil {
  97. var gerr *googleapi.Error
  98. if errors.As(err, &gerr) {
  99. return errors.New(gerr.Message)
  100. }
  101. return err
  102. }
  103. return nil
  104. }
  105. func (g *Client) GetUsers(filters []string) ([]idp.User, error) {
  106. var retval []idp.User
  107. err := g.service.Users.List().
  108. Customer("my_customer").
  109. Fields("users(id,primaryEmail,name,suspended,archived)", "nextPageToken").
  110. Pages(context.TODO(), func(users *admindir.Users) error {
  111. for _, user := range users.Users {
  112. var keep bool
  113. if len(filters) > 0 {
  114. for _, filter := range filters {
  115. if strings.HasPrefix(user.PrimaryEmail, filter) {
  116. keep = true
  117. }
  118. }
  119. } else {
  120. keep = true
  121. }
  122. if !keep {
  123. continue
  124. }
  125. retval = append(retval, idp.User{
  126. ID: user.Id,
  127. Username: user.PrimaryEmail,
  128. DisplayName: user.Name.FullName,
  129. AccountDisabled: user.Suspended,
  130. AccountArchived: user.Archived,
  131. })
  132. }
  133. return nil
  134. })
  135. return retval, err
  136. }
  137. func (g *Client) GetGroups(filters []string) ([]idp.Group, error) {
  138. var retval []idp.Group
  139. err := g.service.Groups.List().
  140. Customer("my_customer").
  141. Fields("groups(id,name)", "nextPageToken").
  142. Pages(context.TODO(), func(groups *admindir.Groups) error {
  143. for _, group := range groups.Groups {
  144. var keep bool
  145. if len(filters) > 0 {
  146. for _, filter := range filters {
  147. if strings.HasPrefix(group.Name, filter) {
  148. keep = true
  149. }
  150. }
  151. } else {
  152. keep = true
  153. }
  154. if !keep {
  155. continue
  156. }
  157. var retvalMembers []string
  158. err := g.service.Members.List(group.Id).
  159. Fields("members(id)", "nextPageToken").
  160. Pages(context.TODO(), func(members *admindir.Members) error {
  161. for _, member := range members.Members {
  162. retvalMembers = append(retvalMembers, member.Id)
  163. }
  164. return nil
  165. })
  166. if err != nil {
  167. return err
  168. }
  169. retval = append(retval, idp.Group{
  170. ID: group.Id,
  171. Name: group.Name,
  172. Members: retvalMembers,
  173. })
  174. }
  175. return nil
  176. })
  177. return retval, err
  178. }
  179. type errorResponse struct {
  180. Error struct {
  181. Code int `json:"code"`
  182. Message string `json:"message"`
  183. Status string `json:"status"`
  184. } `json:"error"`
  185. }