2023-06-20 19:15:36 +02:00
|
|
|
package idp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
2023-09-04 17:03:44 +02:00
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
2023-06-20 19:15:36 +02:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"golang.org/x/oauth2/google"
|
|
|
|
admin "google.golang.org/api/admin/directory/v1"
|
|
|
|
"google.golang.org/api/option"
|
2023-10-03 16:40:28 +02:00
|
|
|
|
|
|
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
2023-06-20 19:15:36 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// GoogleWorkspaceManager Google Workspace manager client instance.
|
|
|
|
type GoogleWorkspaceManager struct {
|
|
|
|
usersService *admin.UsersService
|
|
|
|
CustomerID string
|
|
|
|
httpClient ManagerHTTPClient
|
|
|
|
credentials ManagerCredentials
|
|
|
|
helper ManagerHelper
|
|
|
|
appMetrics telemetry.AppMetrics
|
|
|
|
}
|
|
|
|
|
|
|
|
// GoogleWorkspaceClientConfig Google Workspace manager client configurations.
|
|
|
|
type GoogleWorkspaceClientConfig struct {
|
|
|
|
ServiceAccountKey string
|
|
|
|
CustomerID string
|
|
|
|
}
|
|
|
|
|
|
|
|
// GoogleWorkspaceCredentials Google Workspace authentication information.
|
|
|
|
type GoogleWorkspaceCredentials struct {
|
|
|
|
clientConfig GoogleWorkspaceClientConfig
|
|
|
|
helper ManagerHelper
|
|
|
|
httpClient ManagerHTTPClient
|
|
|
|
appMetrics telemetry.AppMetrics
|
|
|
|
}
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
func (gc *GoogleWorkspaceCredentials) Authenticate(_ context.Context) (JWTToken, error) {
|
2023-06-20 19:15:36 +02:00
|
|
|
return JWTToken{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewGoogleWorkspaceManager creates a new instance of the GoogleWorkspaceManager.
|
2024-07-03 11:33:02 +02:00
|
|
|
func NewGoogleWorkspaceManager(ctx context.Context, config GoogleWorkspaceClientConfig, appMetrics telemetry.AppMetrics) (*GoogleWorkspaceManager, error) {
|
2023-06-20 19:15:36 +02:00
|
|
|
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
|
|
|
httpTransport.MaxIdleConns = 5
|
|
|
|
|
|
|
|
httpClient := &http.Client{
|
|
|
|
Timeout: 10 * time.Second,
|
|
|
|
Transport: httpTransport,
|
|
|
|
}
|
|
|
|
helper := JsonParser{}
|
|
|
|
|
|
|
|
if config.CustomerID == "" {
|
|
|
|
return nil, fmt.Errorf("google IdP configuration is incomplete, CustomerID is missing")
|
|
|
|
}
|
|
|
|
|
|
|
|
credentials := &GoogleWorkspaceCredentials{
|
|
|
|
clientConfig: config,
|
|
|
|
httpClient: httpClient,
|
|
|
|
helper: helper,
|
|
|
|
appMetrics: appMetrics,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new Admin SDK Directory service client
|
2024-07-03 11:33:02 +02:00
|
|
|
adminCredentials, err := getGoogleCredentials(ctx, config.ServiceAccountKey)
|
2023-06-20 19:15:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
service, err := admin.NewService(context.Background(),
|
2023-10-03 16:40:28 +02:00
|
|
|
option.WithScopes(admin.AdminDirectoryUserReadonlyScope),
|
2023-06-20 19:15:36 +02:00
|
|
|
option.WithCredentials(adminCredentials),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &GoogleWorkspaceManager{
|
|
|
|
usersService: service.Users,
|
|
|
|
CustomerID: config.CustomerID,
|
|
|
|
httpClient: httpClient,
|
|
|
|
credentials: credentials,
|
|
|
|
helper: helper,
|
|
|
|
appMetrics: appMetrics,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (gm *GoogleWorkspaceManager) UpdateUserAppMetadata(_ context.Context, _ string, _ AppMetadata) error {
|
2023-06-20 19:15:36 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUserDataByID requests user data from Google Workspace via ID.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (gm *GoogleWorkspaceManager) GetUserDataByID(_ context.Context, userID string, appMetadata AppMetadata) (*UserData, error) {
|
2023-10-11 16:09:30 +02:00
|
|
|
user, err := gm.usersService.Get(userID).Do()
|
2023-06-20 19:15:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if gm.appMetrics != nil {
|
|
|
|
gm.appMetrics.IDPMetrics().CountGetUserDataByID()
|
|
|
|
}
|
|
|
|
|
2023-10-03 16:40:28 +02:00
|
|
|
userData := parseGoogleWorkspaceUser(user)
|
|
|
|
userData.AppMetadata = appMetadata
|
|
|
|
|
|
|
|
return userData, nil
|
2023-06-20 19:15:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetAccount returns all the users for a given profile.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (gm *GoogleWorkspaceManager) GetAccount(_ context.Context, accountID string) ([]*UserData, error) {
|
2023-10-11 16:09:30 +02:00
|
|
|
users, err := gm.getAllUsers()
|
2023-06-20 19:15:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-11 16:09:30 +02:00
|
|
|
if gm.appMetrics != nil {
|
|
|
|
gm.appMetrics.IDPMetrics().CountGetAccount()
|
|
|
|
}
|
2023-06-20 19:15:36 +02:00
|
|
|
|
2023-10-11 16:09:30 +02:00
|
|
|
for index, user := range users {
|
|
|
|
user.AppMetadata.WTAccountID = accountID
|
|
|
|
users[index] = user
|
2023-06-20 19:15:36 +02:00
|
|
|
}
|
|
|
|
|
2023-10-11 16:09:30 +02:00
|
|
|
return users, nil
|
2023-06-20 19:15:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
|
|
|
// It returns a list of users indexed by accountID.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (gm *GoogleWorkspaceManager) GetAllAccounts(_ context.Context) (map[string][]*UserData, error) {
|
2023-10-11 16:09:30 +02:00
|
|
|
users, err := gm.getAllUsers()
|
2023-06-20 19:15:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-11 16:09:30 +02:00
|
|
|
indexedUsers := make(map[string][]*UserData)
|
|
|
|
indexedUsers[UnsetAccountID] = append(indexedUsers[UnsetAccountID], users...)
|
|
|
|
|
2023-06-20 19:15:36 +02:00
|
|
|
if gm.appMetrics != nil {
|
|
|
|
gm.appMetrics.IDPMetrics().CountGetAllAccounts()
|
|
|
|
}
|
|
|
|
|
2023-10-11 16:09:30 +02:00
|
|
|
return indexedUsers, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAllUsers returns all users in a Google Workspace account filtered by customer ID.
|
|
|
|
func (gm *GoogleWorkspaceManager) getAllUsers() ([]*UserData, error) {
|
|
|
|
users := make([]*UserData, 0)
|
|
|
|
pageToken := ""
|
|
|
|
for {
|
|
|
|
call := gm.usersService.List().Customer(gm.CustomerID).MaxResults(500)
|
|
|
|
if pageToken != "" {
|
|
|
|
call.PageToken(pageToken)
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := call.Do()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, user := range resp.Users {
|
|
|
|
users = append(users, parseGoogleWorkspaceUser(user))
|
|
|
|
}
|
|
|
|
|
|
|
|
pageToken = resp.NextPageToken
|
|
|
|
if pageToken == "" {
|
|
|
|
break
|
|
|
|
}
|
2023-06-20 19:15:36 +02:00
|
|
|
}
|
|
|
|
|
2023-10-11 16:09:30 +02:00
|
|
|
return users, nil
|
2023-06-20 19:15:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// CreateUser creates a new user in Google Workspace and sends an invitation.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (gm *GoogleWorkspaceManager) CreateUser(_ context.Context, _, _, _, _ string) (*UserData, error) {
|
2023-10-03 16:40:28 +02:00
|
|
|
return nil, fmt.Errorf("method CreateUser not implemented")
|
2023-06-20 19:15:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetUserByEmail searches users with a given email.
|
|
|
|
// If no users have been found, this function returns an empty list.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (gm *GoogleWorkspaceManager) GetUserByEmail(_ context.Context, email string) ([]*UserData, error) {
|
2023-10-11 16:09:30 +02:00
|
|
|
user, err := gm.usersService.Get(email).Do()
|
2023-06-20 19:15:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if gm.appMetrics != nil {
|
|
|
|
gm.appMetrics.IDPMetrics().CountGetUserByEmail()
|
|
|
|
}
|
|
|
|
|
|
|
|
users := make([]*UserData, 0)
|
2023-10-03 16:40:28 +02:00
|
|
|
users = append(users, parseGoogleWorkspaceUser(user))
|
2023-06-20 19:15:36 +02:00
|
|
|
|
|
|
|
return users, nil
|
|
|
|
}
|
|
|
|
|
2023-07-03 12:20:19 +02:00
|
|
|
// InviteUserByID resend invitations to users who haven't activated,
|
|
|
|
// their accounts prior to the expiration period.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (gm *GoogleWorkspaceManager) InviteUserByID(_ context.Context, _ string) error {
|
2023-07-03 12:20:19 +02:00
|
|
|
return fmt.Errorf("method InviteUserByID not implemented")
|
|
|
|
}
|
|
|
|
|
2023-09-19 18:08:40 +02:00
|
|
|
// DeleteUser from GoogleWorkspace.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (gm *GoogleWorkspaceManager) DeleteUser(_ context.Context, userID string) error {
|
2023-09-19 18:08:40 +02:00
|
|
|
if err := gm.usersService.Delete(userID).Do(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if gm.appMetrics != nil {
|
|
|
|
gm.appMetrics.IDPMetrics().CountDeleteUser()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-20 19:15:36 +02:00
|
|
|
// getGoogleCredentials retrieves Google credentials based on the provided serviceAccountKey.
|
|
|
|
// It decodes the base64-encoded serviceAccountKey and attempts to obtain credentials using it.
|
|
|
|
// If that fails, it falls back to using the default Google credentials path.
|
|
|
|
// It returns the retrieved credentials or an error if unsuccessful.
|
2024-07-03 11:33:02 +02:00
|
|
|
func getGoogleCredentials(ctx context.Context, serviceAccountKey string) (*google.Credentials, error) {
|
|
|
|
log.WithContext(ctx).Debug("retrieving google credentials from the base64 encoded service account key")
|
2023-06-20 19:15:36 +02:00
|
|
|
decodeKey, err := base64.StdEncoding.DecodeString(serviceAccountKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to decode service account key: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
creds, err := google.CredentialsFromJSON(
|
|
|
|
context.Background(),
|
|
|
|
decodeKey,
|
2023-10-03 16:40:28 +02:00
|
|
|
admin.AdminDirectoryUserReadonlyScope,
|
2023-06-20 19:15:36 +02:00
|
|
|
)
|
|
|
|
if err == nil {
|
2023-09-04 17:03:44 +02:00
|
|
|
// No need to fallback to the default Google credentials path
|
|
|
|
return creds, nil
|
2023-06-20 19:15:36 +02:00
|
|
|
}
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
log.WithContext(ctx).Debugf("failed to retrieve Google credentials from ServiceAccountKey: %v", err)
|
|
|
|
log.WithContext(ctx).Debug("falling back to default google credentials location")
|
2023-06-20 19:15:36 +02:00
|
|
|
|
|
|
|
creds, err = google.FindDefaultCredentials(
|
|
|
|
context.Background(),
|
2023-10-03 16:40:28 +02:00
|
|
|
admin.AdminDirectoryUserReadonlyScope,
|
2023-06-20 19:15:36 +02:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return creds, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseGoogleWorkspaceUser parse google user to UserData.
|
2023-10-03 16:40:28 +02:00
|
|
|
func parseGoogleWorkspaceUser(user *admin.User) *UserData {
|
2023-06-20 19:15:36 +02:00
|
|
|
return &UserData{
|
2023-10-03 16:40:28 +02:00
|
|
|
ID: user.Id,
|
|
|
|
Email: user.PrimaryEmail,
|
|
|
|
Name: user.Name.FullName,
|
|
|
|
}
|
2023-06-20 19:15:36 +02:00
|
|
|
}
|