diff --git a/pkg/app/api/api/web_api.go b/pkg/app/api/api/web_api.go index 211ca6819a..e2881b3e80 100644 --- a/pkg/app/api/api/web_api.go +++ b/pkg/app/api/api/web_api.go @@ -40,6 +40,7 @@ type WebAPI struct { environmentStore datastore.EnvironmentStore deploymentStore datastore.DeploymentStore pipedStore datastore.PipedStore + projectStore datastore.ProjectStore stageLogStore stagelogstore.Store applicationLiveStateStore applicationlivestatestore.Store commandStore commandstore.Store @@ -54,6 +55,7 @@ func NewWebAPI(ds datastore.DataStore, sls stagelogstore.Store, alss application environmentStore: datastore.NewEnvironmentStore(ds), deploymentStore: datastore.NewDeploymentStore(ds), pipedStore: datastore.NewPipedStore(ds), + projectStore: datastore.NewProjectStore(ds), stageLogStore: sls, applicationLiveStateStore: alss, commandStore: cmds, @@ -694,8 +696,31 @@ func (a *WebAPI) GetApplicationLiveState(ctx context.Context, req *webservice.Ge }, nil } +// GetProject gets the specified porject without sensitive data. func (a *WebAPI) GetProject(ctx context.Context, req *webservice.GetProjectRequest) (*webservice.GetProjectResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + project, err := a.getProject(ctx, req.ProjectId) + if err != nil { + return nil, err + } + + // Redact all sensitive data inside project message before sending to the client. + project.RedactSensitiveData() + + return &webservice.GetProjectResponse{ + Project: project, + }, nil +} + +func (a *WebAPI) getProject(ctx context.Context, projectID string) (*model.Project, error) { + project, err := a.projectStore.GetProject(ctx, projectID) + if errors.Is(err, datastore.ErrNotFound) { + return nil, status.Error(codes.NotFound, "project is not found") + } + if err != nil { + a.logger.Error("failed to get project", zap.Error(err)) + return nil, status.Error(codes.Internal, "failed to get project") + } + return project, nil } // GetMe gets information about the current user. diff --git a/pkg/app/api/service/webservice/service.go b/pkg/app/api/service/webservice/service.go index 462ba341d2..90095415fc 100644 --- a/pkg/app/api/service/webservice/service.go +++ b/pkg/app/api/service/webservice/service.go @@ -14,6 +14,7 @@ package webservice +// HasLabel checks if DeploymentConfigTemplate has the given label. func (t *DeploymentConfigTemplate) HasLabel(label DeploymentConfigTemplateLabel) bool { for _, l := range t.Labels { if l == label { diff --git a/pkg/app/api/service/webservice/service.proto b/pkg/app/api/service/webservice/service.proto index 3bc7195b03..5b8ba13d49 100644 --- a/pkg/app/api/service/webservice/service.proto +++ b/pkg/app/api/service/webservice/service.proto @@ -27,6 +27,7 @@ import "pkg/model/deployment.proto"; import "pkg/model/logblock.proto"; import "pkg/model/piped.proto"; import "pkg/model/role.proto"; +import "pkg/model/project.proto"; import "google/protobuf/wrappers.proto"; // WebService contains all RPC definitions for web client. @@ -268,9 +269,11 @@ message GetApplicationLiveStateResponse { } message GetProjectRequest { + string project_id = 1 [(validate.rules).string.min_len = 1]; } message GetProjectResponse { + model.Project project = 1; } message GetMeRequest { diff --git a/pkg/model/BUILD.bazel b/pkg/model/BUILD.bazel index c5c7a1a083..541b17dbe8 100644 --- a/pkg/model/BUILD.bazel +++ b/pkg/model/BUILD.bazel @@ -67,7 +67,10 @@ go_library( go_test( name = "go_default_test", size = "small", - srcs = ["piped_test.go"], + srcs = [ + "piped_test.go", + "project_test.go", + ], embed = [":go_default_library"], deps = ["@com_github_stretchr_testify//assert:go_default_library"], ) diff --git a/pkg/model/project.go b/pkg/model/project.go index 42c6181f7a..e0d7227a12 100644 --- a/pkg/model/project.go +++ b/pkg/model/project.go @@ -28,6 +28,21 @@ var ( githubScopes = []string{"read:org"} ) +// RedactSensitiveData redacts sensitive data. +func (p *Project) RedactSensitiveData() { + if p.StaticAdmin != nil { + p.StaticAdmin.RedactSensitiveData() + } + if p.Sso != nil { + p.Sso.RedactSensitiveData() + } +} + +// RedactSensitiveData redacts sensitive data. +func (p *ProjectStaticUser) RedactSensitiveData() { + p.PasswordHash = redactedMessage +} + // Auth confirms username and password. func (p *ProjectStaticUser) Auth(username, password string) error { if username == "" { @@ -45,6 +60,15 @@ func (p *ProjectStaticUser) Auth(username, password string) error { return nil } +// RedactSensitiveData redacts sensitive data. +func (p *ProjectSingleSignOn) RedactSensitiveData() { + if p.Github != nil { + p.Github.RedactSensitiveData() + } + if p.Google != nil { + } +} + // GenerateAuthCodeURL generates an auth URL for the specified configuration. func (p *ProjectSingleSignOn) GenerateAuthCodeURL(project, apiURL, callbackPath, state string) (string, error) { switch p.Provider { @@ -58,6 +82,12 @@ func (p *ProjectSingleSignOn) GenerateAuthCodeURL(project, apiURL, callbackPath, } } +// RedactSensitiveData redacts sensitive data. +func (p *ProjectSingleSignOn_GitHub) RedactSensitiveData() { + p.ClientId = redactedMessage + p.ClientSecret = redactedMessage +} + // GenerateAuthCodeURL generates an auth URL for the specified configuration. func (p *ProjectSingleSignOn_GitHub) GenerateAuthCodeURL(project, apiURL, callbackPath, state string) (string, error) { u, err := url.Parse(p.BaseUrl) diff --git a/pkg/model/project_test.go b/pkg/model/project_test.go new file mode 100644 index 0000000000..21ae0bed21 --- /dev/null +++ b/pkg/model/project_test.go @@ -0,0 +1,62 @@ +// Copyright 2020 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRedactSensitiveData(t *testing.T) { + cases := []struct { + name string + project Project + expect Project + }{ + { + name: "redact", + project: Project{ + StaticAdmin: &ProjectStaticUser{ + PasswordHash: "raw", + }, + Sso: &ProjectSingleSignOn{ + Github: &ProjectSingleSignOn_GitHub{ + ClientId: "raw", + ClientSecret: "raw", + }, + }, + }, + expect: Project{ + StaticAdmin: &ProjectStaticUser{ + PasswordHash: "redacted", + }, + Sso: &ProjectSingleSignOn{ + Github: &ProjectSingleSignOn_GitHub{ + ClientId: "redacted", + ClientSecret: "redacted", + }, + }, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + tc.project.RedactSensitiveData() + assert.Equal(t, tc.expect, tc.project) + }) + } +}