| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | package api | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/api/dtos" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/api/response" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/components/apikeygen" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/models" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/serviceaccounts" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/web" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const failedToDeleteMsg = "Failed to delete API key" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-18 18:43:33 +08:00
										 |  |  | type TokenDTO struct { | 
					
						
							|  |  |  | 	Id                     int64           `json:"id"` | 
					
						
							|  |  |  | 	Name                   string          `json:"name"` | 
					
						
							|  |  |  | 	Role                   models.RoleType `json:"role"` | 
					
						
							|  |  |  | 	Created                *time.Time      `json:"created"` | 
					
						
							|  |  |  | 	Expiration             *time.Time      `json:"expiration"` | 
					
						
							|  |  |  | 	SecondsUntilExpiration *float64        `json:"secondsUntilExpiration"` | 
					
						
							|  |  |  | 	HasExpired             bool            `json:"hasExpired"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func hasExpired(expiration *int64) bool { | 
					
						
							|  |  |  | 	if expiration == nil { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	v := time.Unix(*expiration, 0) | 
					
						
							|  |  |  | 	return (v).Before(time.Now()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const sevenDaysAhead = 7 * 24 * time.Hour | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | func (api *ServiceAccountsAPI) ListTokens(ctx *models.ReqContext) response.Response { | 
					
						
							|  |  |  | 	saID, err := strconv.ParseInt(web.Params(ctx.Req)[":serviceAccountId"], 10, 64) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return response.Error(http.StatusBadRequest, "serviceAccountId is invalid", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if saTokens, err := api.store.ListTokens(ctx.Req.Context(), ctx.OrgId, saID); err == nil { | 
					
						
							| 
									
										
										
										
											2022-02-18 18:43:33 +08:00
										 |  |  | 		result := make([]*TokenDTO, len(saTokens)) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 		for i, t := range saTokens { | 
					
						
							|  |  |  | 			var expiration *time.Time = nil | 
					
						
							| 
									
										
										
										
											2022-02-18 18:43:33 +08:00
										 |  |  | 			var secondsUntilExpiration float64 = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			isExpired := hasExpired(t.Expires) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 			if t.Expires != nil { | 
					
						
							|  |  |  | 				v := time.Unix(*t.Expires, 0) | 
					
						
							|  |  |  | 				expiration = &v | 
					
						
							| 
									
										
										
										
											2022-02-18 18:43:33 +08:00
										 |  |  | 				if !isExpired && (*expiration).Before(time.Now().Add(sevenDaysAhead)) { | 
					
						
							|  |  |  | 					secondsUntilExpiration = time.Until(*expiration).Seconds() | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-02-18 18:43:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			result[i] = &TokenDTO{ | 
					
						
							|  |  |  | 				Id:                     t.Id, | 
					
						
							|  |  |  | 				Name:                   t.Name, | 
					
						
							|  |  |  | 				Role:                   t.Role, | 
					
						
							|  |  |  | 				Created:                &t.Created, | 
					
						
							|  |  |  | 				Expiration:             expiration, | 
					
						
							|  |  |  | 				SecondsUntilExpiration: &secondsUntilExpiration, | 
					
						
							|  |  |  | 				HasExpired:             isExpired, | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 		return response.JSON(http.StatusOK, result) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 		return response.Error(http.StatusInternalServerError, "Internal server error", err) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CreateNewToken adds an API key to a service account
 | 
					
						
							|  |  |  | func (api *ServiceAccountsAPI) CreateToken(c *models.ReqContext) response.Response { | 
					
						
							|  |  |  | 	saID, err := strconv.ParseInt(web.Params(c.Req)[":serviceAccountId"], 10, 64) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return response.Error(http.StatusBadRequest, "serviceAccountId is invalid", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// confirm service account exists
 | 
					
						
							|  |  |  | 	if _, err := api.store.RetrieveServiceAccount(c.Req.Context(), c.OrgId, saID); err != nil { | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 		case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound): | 
					
						
							|  |  |  | 			return response.Error(http.StatusNotFound, "Failed to retrieve service account", err) | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			return response.Error(http.StatusInternalServerError, "Failed to retrieve service account", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cmd := models.AddApiKeyCommand{} | 
					
						
							|  |  |  | 	if err := web.Bind(c.Req, &cmd); err != nil { | 
					
						
							|  |  |  | 		return response.Error(http.StatusBadRequest, "bad request data", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Force affected service account to be the one referenced in the URL
 | 
					
						
							|  |  |  | 	cmd.OrgId = c.OrgId | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !cmd.Role.IsValid() { | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 		return response.Error(http.StatusBadRequest, "Invalid role specified", nil) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if api.cfg.ApiKeyMaxSecondsToLive != -1 { | 
					
						
							|  |  |  | 		if cmd.SecondsToLive == 0 { | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 			return response.Error(http.StatusBadRequest, "Number of seconds before expiration should be set", nil) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if cmd.SecondsToLive > api.cfg.ApiKeyMaxSecondsToLive { | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 			return response.Error(http.StatusBadRequest, "Number of seconds before expiration is greater than the global limit", nil) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	newKeyInfo, err := apikeygen.New(cmd.OrgId, cmd.Name) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 		return response.Error(http.StatusInternalServerError, "Generating API key failed", err) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cmd.Key = newKeyInfo.HashedKey | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-28 18:30:45 +08:00
										 |  |  | 	if err := api.store.AddServiceAccountToken(c.Req.Context(), saID, &cmd); err != nil { | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 		if errors.Is(err, models.ErrInvalidApiKeyExpiration) { | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 			return response.Error(http.StatusBadRequest, err.Error(), nil) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if errors.Is(err, models.ErrDuplicateApiKey) { | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 			return response.Error(http.StatusConflict, err.Error(), nil) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 		return response.Error(http.StatusInternalServerError, "Failed to add API Key", err) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	result := &dtos.NewApiKeyResult{ | 
					
						
							|  |  |  | 		ID:   cmd.Result.Id, | 
					
						
							|  |  |  | 		Name: cmd.Result.Name, | 
					
						
							|  |  |  | 		Key:  newKeyInfo.ClientSecret, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 	return response.JSON(http.StatusOK, result) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DeleteToken deletes service account tokens
 | 
					
						
							|  |  |  | func (api *ServiceAccountsAPI) DeleteToken(c *models.ReqContext) response.Response { | 
					
						
							|  |  |  | 	saID, err := strconv.ParseInt(web.Params(c.Req)[":serviceAccountId"], 10, 64) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-02-28 18:30:45 +08:00
										 |  |  | 		return response.Error(http.StatusBadRequest, "Service Account ID is invalid", err) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// confirm service account exists
 | 
					
						
							|  |  |  | 	if _, err := api.store.RetrieveServiceAccount(c.Req.Context(), c.OrgId, saID); err != nil { | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 		case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound): | 
					
						
							|  |  |  | 			return response.Error(http.StatusNotFound, "Failed to retrieve service account", err) | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			return response.Error(http.StatusInternalServerError, "Failed to retrieve service account", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tokenID, err := strconv.ParseInt(web.Params(c.Req)[":tokenId"], 10, 64) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-02-28 18:30:45 +08:00
										 |  |  | 		return response.Error(http.StatusBadRequest, "Token ID is invalid", err) | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-28 18:30:45 +08:00
										 |  |  | 	if err = api.store.DeleteServiceAccountToken(c.Req.Context(), c.OrgId, saID, tokenID); err != nil { | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 		status := http.StatusNotFound | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 		if err != nil && !errors.Is(err, models.ErrApiKeyNotFound) { | 
					
						
							| 
									
										
										
										
											2022-02-17 22:00:56 +08:00
										 |  |  | 			status = http.StatusInternalServerError | 
					
						
							| 
									
										
										
										
											2022-02-07 21:51:54 +08:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			err = models.ErrApiKeyNotFound | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return response.Error(status, failedToDeleteMsg, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return response.Success("API key deleted") | 
					
						
							|  |  |  | } |