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 }