221 lines
7.1 KiB
Go
221 lines
7.1 KiB
Go
package github
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
const (
|
|
maxDeploymentAppManifestConversionCodeLength = 1024
|
|
maxDeploymentAppProviderErrorLength = 256
|
|
)
|
|
|
|
func (c *Controller) httpListAppRegistrations(ctx *gin.Context) {
|
|
result, err := c.service.ListAppRegistrationCatalog(
|
|
ctx.Request.Context(), currentGitHubUserID(ctx), ctx.Query("workspace_id"),
|
|
)
|
|
if err != nil {
|
|
writeGitHubAuthError(ctx, err)
|
|
return
|
|
}
|
|
ctx.Header("Cache-Control", "no-store")
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
func (c *Controller) httpStartAppRegistrationManifest(ctx *gin.Context) {
|
|
var request AppRegistrationManifestStartRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
writeAppRegistrationInvalidRequest(ctx)
|
|
return
|
|
}
|
|
result, err := c.service.StartAppRegistrationManifest(
|
|
ctx.Request.Context(), currentGitHubUserID(ctx), request,
|
|
)
|
|
if err != nil {
|
|
writeGitHubAuthError(ctx, err)
|
|
return
|
|
}
|
|
ctx.Header("Cache-Control", "no-store")
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
func (c *Controller) httpCompleteAppRegistrationManifest(ctx *gin.Context) {
|
|
state, code, providerError := ctx.Query("state"), ctx.Query("code"), ctx.Query("error")
|
|
if !validGitHubCallbackState(state) ||
|
|
len(code) > maxDeploymentAppManifestConversionCodeLength ||
|
|
len(providerError) > maxDeploymentAppProviderErrorLength {
|
|
redirectAppRegistrationCallback(ctx, "", "github_app_invalid_callback")
|
|
return
|
|
}
|
|
result, err := c.service.CompleteAppRegistrationManifest(
|
|
ctx.Request.Context(), ctx.Param("registrationId"), AppRegistrationManifestCallback{
|
|
State: state, Code: code, Error: providerError,
|
|
},
|
|
)
|
|
if err != nil {
|
|
workspaceID := appRegistrationFlowWorkspace(ctx, c.service, state)
|
|
redirectAppRegistrationCallback(ctx, workspaceID, githubAuthErrorCode(err))
|
|
return
|
|
}
|
|
redirectAppRegistrationCallback(ctx, result.WorkspaceID, "app_registered")
|
|
}
|
|
|
|
func appRegistrationFlowWorkspace(ctx *gin.Context, service *Service, state string) string {
|
|
if service == nil || service.store == nil || state == "" {
|
|
return ""
|
|
}
|
|
digest := stateDigest(state)
|
|
flow, err := service.store.GetDeploymentAppRegistrationFlow(ctx.Request.Context(), digest)
|
|
if err != nil || flow == nil {
|
|
return ""
|
|
}
|
|
return flow.WorkspaceID
|
|
}
|
|
|
|
func stateDigest(state string) string {
|
|
digest := sha256Digest(strings.TrimSpace(state))
|
|
return stateDigestString(digest)
|
|
}
|
|
|
|
func sha256Digest(value string) [32]byte {
|
|
return sha256.Sum256([]byte(value))
|
|
}
|
|
|
|
func (c *Controller) httpImportAppRegistration(ctx *gin.Context) {
|
|
var request AppRegistrationImportRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
writeAppRegistrationInvalidRequest(ctx)
|
|
return
|
|
}
|
|
registration, err := c.service.ImportAppRegistration(
|
|
ctx.Request.Context(), currentGitHubUserID(ctx), request,
|
|
)
|
|
if err != nil {
|
|
writeGitHubAuthError(ctx, err)
|
|
return
|
|
}
|
|
ctx.Header("Cache-Control", "no-store")
|
|
ctx.JSON(http.StatusCreated, registration)
|
|
}
|
|
|
|
func (c *Controller) httpPrepareAppRegistrationImport(ctx *gin.Context) {
|
|
var request AppRegistrationImportPrepareRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
writeAppRegistrationInvalidRequest(ctx)
|
|
return
|
|
}
|
|
preparation, err := c.service.PrepareAppRegistrationImport(
|
|
ctx.Request.Context(), currentGitHubUserID(ctx), request,
|
|
)
|
|
if err != nil {
|
|
writeGitHubAuthError(ctx, err)
|
|
return
|
|
}
|
|
ctx.Header("Cache-Control", "no-store")
|
|
ctx.JSON(http.StatusCreated, preparation)
|
|
}
|
|
|
|
func (c *Controller) httpRenameAppRegistration(ctx *gin.Context) {
|
|
var request struct {
|
|
DisplayName string `json:"display_name"`
|
|
}
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
writeAppRegistrationInvalidRequest(ctx)
|
|
return
|
|
}
|
|
registration, err := c.service.RenameAppRegistration(
|
|
ctx.Request.Context(), currentGitHubUserID(ctx), ctx.Param("registrationId"), request.DisplayName,
|
|
)
|
|
if err != nil {
|
|
writeGitHubAuthError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, registration)
|
|
}
|
|
|
|
func (c *Controller) httpDeleteAppRegistration(ctx *gin.Context) {
|
|
err := c.service.DeleteAppRegistration(
|
|
ctx.Request.Context(), currentGitHubUserID(ctx), ctx.Param("registrationId"),
|
|
)
|
|
if err != nil {
|
|
var inUse *AppRegistrationInUseError
|
|
if errors.As(err, &inUse) {
|
|
ctx.JSON(http.StatusConflict, gin.H{
|
|
"code": "github_app_registration_in_use", "error": inUse.Error(),
|
|
"binding_count": inUse.BindingCount,
|
|
})
|
|
return
|
|
}
|
|
writeGitHubAuthError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"deleted": true})
|
|
}
|
|
|
|
func writeAppRegistrationInvalidRequest(ctx *gin.Context) {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
|
"code": "github_app_invalid_request", "error": "invalid GitHub App registration request",
|
|
})
|
|
}
|
|
|
|
func redirectAppRegistrationCallback(ctx *gin.Context, workspaceID, result string) {
|
|
query := url.Values{"github_result": []string{result}}
|
|
if workspaceID != "" {
|
|
query.Set("workspace_id", workspaceID)
|
|
}
|
|
ctx.Header("Cache-Control", "no-store")
|
|
ctx.Header("Referrer-Policy", "no-referrer")
|
|
ctx.Redirect(http.StatusSeeOther, "/settings/integrations/github?"+query.Encode())
|
|
}
|
|
|
|
func deploymentAppHTTPError(err error) (int, string, bool) {
|
|
switch {
|
|
case errors.Is(err, ErrDeploymentAppOperatorRequired):
|
|
return http.StatusForbidden, "github_app_operator_required", true
|
|
case errors.Is(err, ErrDeploymentAppRegistrationCancelled):
|
|
return http.StatusBadRequest, "github_app_registration_cancelled", true
|
|
case errors.Is(err, ErrAppRegistrationNotFound):
|
|
return http.StatusNotFound, "github_app_registration_not_found", true
|
|
case errors.Is(err, ErrDeploymentAppInUse):
|
|
return http.StatusConflict, "github_app_registration_in_use", true
|
|
case errors.Is(err, ErrDeploymentAppManifestOwnerInvalid),
|
|
errors.Is(err, ErrAppRegistrationIDInvalid),
|
|
errors.Is(err, ErrAppRegistrationVisibilityInvalid):
|
|
return http.StatusBadRequest, "github_app_invalid_request", true
|
|
case errors.Is(err, ErrPublicGitHubBaseURLInvalid),
|
|
errors.Is(err, ErrPublicGitHubBaseURLNotGlobal),
|
|
errors.Is(err, ErrPublicGitHubBaseURLUnresolvable):
|
|
return http.StatusBadRequest, "github_app_public_url_invalid", true
|
|
case errors.Is(err, ErrDeploymentAppManifestStateUnavailable),
|
|
errors.Is(err, ErrDeploymentAppIdentityMismatch),
|
|
errors.Is(err, ErrDeploymentAppPolicyMismatch):
|
|
return http.StatusBadRequest, "github_app_invalid_callback", true
|
|
case errors.Is(err, ErrAppRegistrationImportPreparationUnavailable):
|
|
return http.StatusBadRequest, "github_app_import_preparation_invalid", true
|
|
}
|
|
var importError *AppRegistrationImportError
|
|
if errors.As(err, &importError) {
|
|
code := "github_app_" + string(importError.Code)
|
|
if importError.Code == AppRegistrationImportAlreadyRegistered {
|
|
return http.StatusConflict, "github_app_already_registered", true
|
|
}
|
|
if importError.Code == AppRegistrationImportInvalidRequest {
|
|
return http.StatusBadRequest, "github_app_invalid_request", true
|
|
}
|
|
if importError.Code == AppRegistrationImportPersistenceFailed {
|
|
return http.StatusInternalServerError, code, true
|
|
}
|
|
return http.StatusUnprocessableEntity, code, true
|
|
}
|
|
var conversionError *ManifestConversionError
|
|
if errors.As(err, &conversionError) {
|
|
return http.StatusBadGateway, string(conversionError.Code), true
|
|
}
|
|
return 0, "", false
|
|
}
|