github.com/esnet/gdg@v0.6.1-0.20240412190737-6b6eba9c14d8/internal/service/user.go (about)

     1  package service
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/esnet/gdg/internal/config"
     8  	"github.com/esnet/gdg/internal/service/filters"
     9  	"github.com/esnet/gdg/internal/tools"
    10  	"github.com/esnet/gdg/internal/types"
    11  	"github.com/gosimple/slug"
    12  	"github.com/grafana/grafana-openapi-client-go/client/admin_users"
    13  	"github.com/grafana/grafana-openapi-client-go/client/users"
    14  	"github.com/grafana/grafana-openapi-client-go/models"
    15  	"github.com/tidwall/pretty"
    16  	"log"
    17  	"log/slog"
    18  	"path/filepath"
    19  	"sort"
    20  	"strings"
    21  )
    22  
    23  func NewUserFilter(label string) filters.Filter {
    24  	filterEntity := filters.NewBaseFilter()
    25  	if label == "" {
    26  		return filterEntity
    27  	}
    28  	filterEntity.AddFilter(filters.AuthLabel, label)
    29  	filterEntity.AddValidation(filters.DefaultFilter, func(i interface{}) bool {
    30  		val, ok := i.(map[filters.FilterType]string)
    31  		if !ok {
    32  			return ok
    33  		}
    34  		if filterEntity.GetFilter(filters.AuthLabel) == "" {
    35  			return true
    36  		}
    37  		return val[filters.AuthLabel] == filterEntity.GetFilter(filters.AuthLabel)
    38  	})
    39  
    40  	return filterEntity
    41  }
    42  
    43  // GetUserInfo get signed-in user info, requires Basic authentication
    44  func (s *DashNGoImpl) GetUserInfo() (*models.UserProfileDTO, error) {
    45  	userInfo, err := s.GetBasicAuthClient().SignedInUser.GetSignedInUser()
    46  	if err == nil {
    47  		return userInfo.GetPayload(), err
    48  	}
    49  	return nil, err
    50  
    51  }
    52  
    53  func (s *DashNGoImpl) DownloadUsers(filter filters.Filter) []string {
    54  	var (
    55  		userData []byte
    56  		err      error
    57  	)
    58  
    59  	userListing := s.ListUsers(filter)
    60  	var importedUsers []string
    61  
    62  	userPath := BuildResourceFolder("", config.UserResource)
    63  	for ndx, user := range userListing {
    64  		if s.isAdminUser(user.ID, user.Name) {
    65  			slog.Info("Skipping admin super user")
    66  			continue
    67  		}
    68  		fileName := filepath.Join(userPath, fmt.Sprintf("%s.json", GetSlug(user.Login)))
    69  		userData, err = json.Marshal(&userListing[ndx])
    70  		if err != nil {
    71  			slog.Error("could not serialize user object for userId", "userID", user.ID)
    72  			continue
    73  		}
    74  		if err = s.storage.WriteFile(fileName, pretty.Pretty(userData)); err != nil {
    75  			slog.Error("Failed to write file", "filename", user.Login, "err", err)
    76  		} else {
    77  			importedUsers = append(importedUsers, fileName)
    78  		}
    79  
    80  	}
    81  	return importedUsers
    82  
    83  }
    84  
    85  func (s *DashNGoImpl) isAdminUser(id int64, name string) bool {
    86  	return id == 1 || name == "admin"
    87  }
    88  
    89  func (s *DashNGoImpl) UploadUsers(filter filters.Filter) []types.UserProfileWithAuth {
    90  	filesInDir, err := s.storage.FindAllFiles(config.Config().GetDefaultGrafanaConfig().GetPath(config.UserResource), false)
    91  	if err != nil {
    92  		slog.Error("failed to list files in directory for userListings", "err", err)
    93  	}
    94  	var userListings []types.UserProfileWithAuth
    95  	var rawUser []byte
    96  	userList := s.ListUsers(filter)
    97  	var currentUsers = make(map[string]*models.UserSearchHitDTO, 0)
    98  
    99  	//Build current User Mapping
   100  	for ndx, i := range userList {
   101  		key := slug.Make(i.Login) + ".json"
   102  		currentUsers[key] = userList[ndx]
   103  	}
   104  
   105  	for _, file := range filesInDir {
   106  		fileLocation := filepath.Join(config.Config().GetDefaultGrafanaConfig().GetPath(config.UserResource), file)
   107  		if strings.HasSuffix(file, ".json") {
   108  			if rawUser, err = s.storage.ReadFile(fileLocation); err != nil {
   109  				slog.Error("failed to read file", "filename", fileLocation, "err", err)
   110  				continue
   111  			}
   112  			if val, ok := currentUsers[filepath.Base(file)]; ok {
   113  				slog.Warn("User already exist, skipping", "username", val.Login)
   114  				continue
   115  			}
   116  			var newUser models.AdminCreateUserForm
   117  
   118  			//generate user password
   119  			password := s.grafanaConf.GetUserSettings().GetPassword(file)
   120  
   121  			var data = make(map[string]interface{})
   122  			if err = json.Unmarshal(rawUser, &data); err != nil {
   123  				slog.Error("failed to unmarshall file", "filename", fileLocation, "err", err)
   124  				continue
   125  			}
   126  			data["password"] = password
   127  
   128  			//Get raw version of payload once more with password
   129  			if rawUser, err = json.Marshal(data); err != nil {
   130  				slog.Error("failed to marshall file to include password", "filename", fileLocation, "err", err)
   131  			}
   132  
   133  			if err = json.Unmarshal(rawUser, &newUser); err != nil {
   134  				slog.Error("failed to unmarshall file", "filename", fileLocation, "err", err)
   135  				continue
   136  			}
   137  
   138  			if newUser.Name == "admin" {
   139  				slog.Info("Skipping admin user")
   140  				continue
   141  			}
   142  			params := admin_users.NewAdminCreateUserParams()
   143  			params.Body = &newUser
   144  			userCreated, err := s.GetBasicAuthClient().AdminUsers.AdminCreateUser(&newUser)
   145  			if err != nil {
   146  				slog.Error("Failed to create user for file", "filename", fileLocation, "err", err)
   147  				continue
   148  			}
   149  			resp, err := s.GetBasicAuthClient().Users.GetUserByID(userCreated.Payload.ID)
   150  			if err != nil {
   151  				slog.Error("unable to read user back from grafana", "username", newUser.Email, "userID", userCreated.GetPayload().ID)
   152  				continue
   153  			}
   154  			userListings = append(userListings, types.UserProfileWithAuth{UserProfileDTO: *resp.GetPayload(), Password: newUser.Password})
   155  		}
   156  	}
   157  
   158  	return userListings
   159  }
   160  
   161  // ListUsers list all grafana users
   162  func (s *DashNGoImpl) ListUsers(filter filters.Filter) []*models.UserSearchHitDTO {
   163  	if !s.grafanaConf.IsBasicAuth() {
   164  		log.Fatal("User listing requires basic auth to be configured.  Token based listing is not supported")
   165  	}
   166  	var filteredUsers []*models.UserSearchHitDTO
   167  	params := users.NewSearchUsersParams()
   168  	params.Page = tools.PtrOf(int64(1))
   169  	params.Perpage = tools.PtrOf(int64(5000))
   170  	usersList, err := s.GetClient().Users.SearchUsers(params)
   171  	if err != nil {
   172  		log.Fatal(err.Error())
   173  	}
   174  	for _, entry := range usersList.GetPayload() {
   175  		if len(entry.AuthLabels) == 0 {
   176  			filteredUsers = append(filteredUsers, entry)
   177  		} else if filter.ValidateAll(map[filters.FilterType]string{filters.AuthLabel: entry.AuthLabels[0]}) {
   178  			filteredUsers = append(filteredUsers, entry)
   179  		}
   180  	}
   181  	sort.Slice(filteredUsers, func(i, j int) bool {
   182  		return filteredUsers[i].ID < filteredUsers[j].ID
   183  	})
   184  	return filteredUsers
   185  }
   186  
   187  // DeleteAllUsers remove all users excluding admin or anything matching the filter
   188  func (s *DashNGoImpl) DeleteAllUsers(filter filters.Filter) []string {
   189  	userListing := s.ListUsers(filter)
   190  	var deletedUsers []string
   191  	for _, user := range userListing {
   192  		if s.isAdminUser(user.ID, user.Name) {
   193  			slog.Info("Skipping admin user")
   194  			continue
   195  
   196  		}
   197  		params := admin_users.NewAdminDeleteUserParams()
   198  		params.UserID = user.ID
   199  		_, err := s.GetBasicAuthClient().AdminUsers.AdminDeleteUser(user.ID)
   200  		if err == nil {
   201  			deletedUsers = append(deletedUsers, user.Email)
   202  		}
   203  	}
   204  	return deletedUsers
   205  
   206  }
   207  
   208  // PromoteUser promote the user to have Admin Access
   209  func (s *DashNGoImpl) PromoteUser(userLogin string) (string, error) {
   210  
   211  	//Get all users
   212  	userListing := s.ListUsers(filters.NewBaseFilter())
   213  	var user *models.UserSearchHitDTO
   214  	for ndx, item := range userListing {
   215  		if item.Email == userLogin {
   216  			user = userListing[ndx]
   217  			break
   218  		}
   219  
   220  	}
   221  
   222  	if user == nil {
   223  		return "", fmt.Errorf("user: '%s' could not be found", userLogin)
   224  	}
   225  	requestBody := &models.AdminUpdateUserPermissionsForm{IsGrafanaAdmin: true}
   226  
   227  	msg, err := s.GetBasicAuthClient().AdminUsers.AdminUpdateUserPermissions(user.ID, requestBody)
   228  	if err != nil {
   229  		errorMsg := fmt.Sprintf("failed to promote user: '%s'", userLogin)
   230  		slog.Error("failed to promote user", "username", userLogin, "err", err)
   231  		return "", errors.New(errorMsg)
   232  	}
   233  
   234  	return msg.GetPayload().Message, nil
   235  
   236  }
   237  
   238  // getUserById get the user by ID
   239  func (s *DashNGoImpl) getUserById(userId int64) (*models.UserProfileDTO, error) {
   240  	resp, err := s.GetClient().Users.GetUserByID(userId)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  	return resp.GetPayload(), nil
   245  }