github.com/argoproj/argo-cd/v2@v2.10.9/server/project/project.go (about)

     1  package project
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"strings"
     8  
     9  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    10  	"github.com/argoproj/pkg/sync"
    11  	"github.com/golang-jwt/jwt/v4"
    12  	"github.com/google/uuid"
    13  	log "github.com/sirupsen/logrus"
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/grpc/status"
    16  	v1 "k8s.io/api/core/v1"
    17  	apierr "k8s.io/apimachinery/pkg/api/errors"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/fields"
    20  	"k8s.io/client-go/kubernetes"
    21  	"k8s.io/client-go/tools/cache"
    22  	"k8s.io/client-go/util/retry"
    23  
    24  	"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
    25  	"github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
    26  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    27  	appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
    28  	listersv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
    29  	"github.com/argoproj/argo-cd/v2/server/deeplinks"
    30  	"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
    31  	"github.com/argoproj/argo-cd/v2/util/argo"
    32  	"github.com/argoproj/argo-cd/v2/util/db"
    33  	jwtutil "github.com/argoproj/argo-cd/v2/util/jwt"
    34  	"github.com/argoproj/argo-cd/v2/util/rbac"
    35  	"github.com/argoproj/argo-cd/v2/util/session"
    36  	"github.com/argoproj/argo-cd/v2/util/settings"
    37  )
    38  
    39  const (
    40  	// JWTTokenSubFormat format of the JWT token subject that Argo CD vends out.
    41  	JWTTokenSubFormat = "proj:%s:%s"
    42  )
    43  
    44  // Server provides a Project service
    45  type Server struct {
    46  	ns            string
    47  	enf           *rbac.Enforcer
    48  	policyEnf     *rbacpolicy.RBACPolicyEnforcer
    49  	appclientset  appclientset.Interface
    50  	kubeclientset kubernetes.Interface
    51  	auditLogger   *argo.AuditLogger
    52  	projectLock   sync.KeyLock
    53  	sessionMgr    *session.SessionManager
    54  	projInformer  cache.SharedIndexInformer
    55  	settingsMgr   *settings.SettingsManager
    56  	db            db.ArgoDB
    57  }
    58  
    59  // NewServer returns a new instance of the Project service
    60  func NewServer(ns string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface, enf *rbac.Enforcer, projectLock sync.KeyLock, sessionMgr *session.SessionManager, policyEnf *rbacpolicy.RBACPolicyEnforcer,
    61  	projInformer cache.SharedIndexInformer, settingsMgr *settings.SettingsManager, db db.ArgoDB) *Server {
    62  	auditLogger := argo.NewAuditLogger(ns, kubeclientset, "argocd-server")
    63  	return &Server{enf: enf, policyEnf: policyEnf, appclientset: appclientset, kubeclientset: kubeclientset, ns: ns, projectLock: projectLock, auditLogger: auditLogger, sessionMgr: sessionMgr,
    64  		projInformer: projInformer, settingsMgr: settingsMgr, db: db}
    65  }
    66  
    67  func validateProject(proj *v1alpha1.AppProject) error {
    68  	err := proj.ValidateProject()
    69  	if err != nil {
    70  		return err
    71  	}
    72  	err = rbac.ValidatePolicy(proj.ProjectPoliciesString())
    73  	if err != nil {
    74  		return status.Errorf(codes.InvalidArgument, "policy syntax error: %s", err.Error())
    75  	}
    76  	return nil
    77  }
    78  
    79  // CreateToken creates a new token to access a project
    80  func (s *Server) CreateToken(ctx context.Context, q *project.ProjectTokenCreateRequest) (*project.ProjectTokenResponse, error) {
    81  	var resp *project.ProjectTokenResponse
    82  	err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
    83  		var createErr error
    84  		resp, createErr = s.createToken(ctx, q)
    85  		return createErr
    86  	})
    87  	return resp, err
    88  }
    89  
    90  func (s *Server) createToken(ctx context.Context, q *project.ProjectTokenCreateRequest) (*project.ProjectTokenResponse, error) {
    91  	prj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Project, metav1.GetOptions{})
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	err = validateProject(prj)
    96  	if err != nil {
    97  		return nil, fmt.Errorf("error validating project: %w", err)
    98  	}
    99  
   100  	s.projectLock.Lock(q.Project)
   101  	defer s.projectLock.Unlock(q.Project)
   102  
   103  	role, _, err := prj.GetRoleByName(q.Role)
   104  	if err != nil {
   105  		return nil, status.Errorf(codes.NotFound, "project '%s' does not have role '%s'", q.Project, q.Role)
   106  	}
   107  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionUpdate, q.Project); err != nil {
   108  		if !jwtutil.IsMember(jwtutil.Claims(ctx.Value("claims")), role.Groups, s.policyEnf.GetScopes()) {
   109  			return nil, err
   110  		}
   111  	}
   112  	id := q.Id
   113  	if err := prj.ValidateJWTTokenID(q.Role, q.Id); err != nil {
   114  		return nil, status.Errorf(codes.InvalidArgument, err.Error())
   115  	}
   116  	if id == "" {
   117  		uniqueId, _ := uuid.NewRandom()
   118  		id = uniqueId.String()
   119  	}
   120  	subject := fmt.Sprintf(JWTTokenSubFormat, q.Project, q.Role)
   121  	jwtToken, err := s.sessionMgr.Create(subject, q.ExpiresIn, id)
   122  	if err != nil {
   123  		return nil, status.Error(codes.InvalidArgument, err.Error())
   124  	}
   125  	parser := jwt.NewParser(jwt.WithoutClaimsValidation())
   126  	claims := jwt.RegisteredClaims{}
   127  	_, _, err = parser.ParseUnverified(jwtToken, &claims)
   128  	if err != nil {
   129  		return nil, status.Error(codes.InvalidArgument, err.Error())
   130  	}
   131  	var issuedAt, expiresAt int64
   132  	if claims.IssuedAt != nil {
   133  		issuedAt = claims.IssuedAt.Unix()
   134  	}
   135  	if claims.ExpiresAt != nil {
   136  		expiresAt = claims.ExpiresAt.Unix()
   137  	}
   138  	id = claims.ID
   139  
   140  	items := append(prj.Status.JWTTokensByRole[q.Role].Items, v1alpha1.JWTToken{IssuedAt: issuedAt, ExpiresAt: expiresAt, ID: id})
   141  	if _, found := prj.Status.JWTTokensByRole[q.Role]; found {
   142  		prj.Status.JWTTokensByRole[q.Role] = v1alpha1.JWTTokens{Items: items}
   143  	} else {
   144  		tokensMap := make(map[string]v1alpha1.JWTTokens)
   145  		tokensMap[q.Role] = v1alpha1.JWTTokens{Items: items}
   146  		prj.Status.JWTTokensByRole = tokensMap
   147  	}
   148  
   149  	prj.NormalizeJWTTokens()
   150  
   151  	_, err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(ctx, prj, metav1.UpdateOptions{})
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	s.logEvent(prj, ctx, argo.EventReasonResourceCreated, "created token")
   156  	return &project.ProjectTokenResponse{Token: jwtToken}, nil
   157  
   158  }
   159  
   160  func (s *Server) ListLinks(ctx context.Context, q *project.ListProjectLinksRequest) (*application.LinksResponse, error) {
   161  	projName := q.GetName()
   162  
   163  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionGet, projName); err != nil {
   164  		log.WithFields(map[string]interface{}{
   165  			"project": projName,
   166  		}).Warnf("unauthorized access to project, error=%v", err.Error())
   167  		return nil, fmt.Errorf("unauthorized access to project %v", projName)
   168  	}
   169  
   170  	proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, projName, metav1.GetOptions{})
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	// sanitize project jwt tokens
   176  	proj.Status = v1alpha1.AppProjectStatus{}
   177  
   178  	obj, err := kube.ToUnstructured(proj)
   179  	if err != nil {
   180  		return nil, fmt.Errorf("error getting application: %w", err)
   181  	}
   182  
   183  	deepLinks, err := s.settingsMgr.GetDeepLinks(settings.ProjectDeepLinks)
   184  	if err != nil {
   185  		return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
   186  	}
   187  
   188  	deeplinksObj := deeplinks.CreateDeepLinksObject(nil, nil, nil, obj)
   189  	finalList, errorList := deeplinks.EvaluateDeepLinksResponse(deeplinksObj, obj.GetName(), deepLinks)
   190  	if len(errorList) > 0 {
   191  		log.Errorf("errorList while evaluating project deep links, %v", strings.Join(errorList, ", "))
   192  	}
   193  
   194  	return finalList, nil
   195  }
   196  
   197  // DeleteToken deletes a token in a project
   198  func (s *Server) DeleteToken(ctx context.Context, q *project.ProjectTokenDeleteRequest) (*project.EmptyResponse, error) {
   199  	var resp *project.EmptyResponse
   200  	err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   201  		var deleteErr error
   202  		resp, deleteErr = s.deleteToken(ctx, q)
   203  		return deleteErr
   204  	})
   205  	return resp, err
   206  }
   207  
   208  func (s *Server) deleteToken(ctx context.Context, q *project.ProjectTokenDeleteRequest) (*project.EmptyResponse, error) {
   209  	prj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Project, metav1.GetOptions{})
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	err = validateProject(prj)
   214  	if err != nil {
   215  		return nil, fmt.Errorf("error validating project: %w", err)
   216  	}
   217  
   218  	s.projectLock.Lock(q.Project)
   219  	defer s.projectLock.Unlock(q.Project)
   220  
   221  	role, roleIndex, err := prj.GetRoleByName(q.Role)
   222  	if err != nil {
   223  		return &project.EmptyResponse{}, nil
   224  	}
   225  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionUpdate, q.Project); err != nil {
   226  		if !jwtutil.IsMember(jwtutil.Claims(ctx.Value("claims")), role.Groups, s.policyEnf.GetScopes()) {
   227  			return nil, err
   228  		}
   229  	}
   230  
   231  	err = prj.RemoveJWTToken(roleIndex, q.Iat, q.Id)
   232  	if err != nil {
   233  		return &project.EmptyResponse{}, nil
   234  	}
   235  
   236  	_, err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(ctx, prj, metav1.UpdateOptions{})
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	s.logEvent(prj, ctx, argo.EventReasonResourceDeleted, "deleted token")
   241  
   242  	return &project.EmptyResponse{}, nil
   243  }
   244  
   245  // Create a new project
   246  func (s *Server) Create(ctx context.Context, q *project.ProjectCreateRequest) (*v1alpha1.AppProject, error) {
   247  	if q.Project == nil {
   248  		return nil, status.Errorf(codes.InvalidArgument, "missing payload 'project' in request")
   249  	}
   250  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionCreate, q.Project.Name); err != nil {
   251  		return nil, err
   252  	}
   253  	q.Project.NormalizePolicies()
   254  	err := validateProject(q.Project)
   255  	if err != nil {
   256  		return nil, fmt.Errorf("error validating project: %w", err)
   257  	}
   258  	res, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Create(ctx, q.Project, metav1.CreateOptions{})
   259  	if apierr.IsAlreadyExists(err) {
   260  		existing, getErr := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Project.Name, metav1.GetOptions{})
   261  		if getErr != nil {
   262  			return nil, status.Errorf(codes.Internal, "unable to check existing project details: %v", getErr)
   263  		}
   264  		if q.GetUpsert() {
   265  			if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionUpdate, q.GetProject().Name); err != nil {
   266  				return nil, err
   267  			}
   268  			existing.Spec = q.GetProject().Spec
   269  			res, err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(ctx, existing, metav1.UpdateOptions{})
   270  		} else {
   271  			if !reflect.DeepEqual(existing.Spec, q.GetProject().Spec) {
   272  				return nil, status.Errorf(codes.InvalidArgument, argo.GenerateSpecIsDifferentErrorMessage("project", existing.Spec, q.GetProject().Spec))
   273  			}
   274  			return existing, nil
   275  		}
   276  	}
   277  	if err == nil {
   278  		s.logEvent(res, ctx, argo.EventReasonResourceCreated, "created project")
   279  	}
   280  	return res, err
   281  }
   282  
   283  // List returns list of projects
   284  func (s *Server) List(ctx context.Context, q *project.ProjectQuery) (*v1alpha1.AppProjectList, error) {
   285  	list, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).List(ctx, metav1.ListOptions{})
   286  	if list != nil {
   287  		newItems := make([]v1alpha1.AppProject, 0)
   288  		for i := range list.Items {
   289  			project := list.Items[i]
   290  			if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionGet, project.Name) {
   291  				newItems = append(newItems, project)
   292  			}
   293  		}
   294  		list.Items = newItems
   295  	}
   296  	return list, err
   297  }
   298  
   299  // GetDetailedProject returns a project with scoped resources
   300  func (s *Server) GetDetailedProject(ctx context.Context, q *project.ProjectQuery) (*project.DetailedProjectsResponse, error) {
   301  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionGet, q.Name); err != nil {
   302  		return nil, err
   303  	}
   304  	proj, repositories, clusters, err := argo.GetAppProjectWithScopedResources(q.Name, listersv1alpha1.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  	proj.NormalizeJWTTokens()
   309  	globalProjects := argo.GetGlobalProjects(proj, listersv1alpha1.NewAppProjectLister(s.projInformer.GetIndexer()), s.settingsMgr)
   310  
   311  	return &project.DetailedProjectsResponse{
   312  		GlobalProjects: globalProjects,
   313  		Project:        proj,
   314  		Repositories:   repositories,
   315  		Clusters:       clusters,
   316  	}, err
   317  }
   318  
   319  // Get returns a project by name
   320  func (s *Server) Get(ctx context.Context, q *project.ProjectQuery) (*v1alpha1.AppProject, error) {
   321  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionGet, q.Name); err != nil {
   322  		return nil, err
   323  	}
   324  	proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Name, metav1.GetOptions{})
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	proj.NormalizeJWTTokens()
   329  	return proj, err
   330  }
   331  
   332  // GetGlobalProjects returns global projects
   333  func (s *Server) GetGlobalProjects(ctx context.Context, q *project.ProjectQuery) (*project.GlobalProjectsResponse, error) {
   334  	projOrig, err := s.Get(ctx, q)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	globalProjects := argo.GetGlobalProjects(projOrig, listersv1alpha1.NewAppProjectLister(s.projInformer.GetIndexer()), s.settingsMgr)
   340  
   341  	res := &project.GlobalProjectsResponse{}
   342  	res.Items = globalProjects
   343  	return res, nil
   344  }
   345  
   346  // Update updates a project
   347  func (s *Server) Update(ctx context.Context, q *project.ProjectUpdateRequest) (*v1alpha1.AppProject, error) {
   348  	if q.Project == nil {
   349  		return nil, status.Errorf(codes.InvalidArgument, "missing payload 'project' in request")
   350  	}
   351  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionUpdate, q.Project.Name); err != nil {
   352  		return nil, err
   353  	}
   354  	q.Project.NormalizePolicies()
   355  	q.Project.NormalizeJWTTokens()
   356  	err := validateProject(q.Project)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  	s.projectLock.Lock(q.Project.Name)
   361  	defer s.projectLock.Unlock(q.Project.Name)
   362  
   363  	oldProj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Project.Name, metav1.GetOptions{})
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	for _, cluster := range difference(q.Project.Spec.DestinationClusters(), oldProj.Spec.DestinationClusters()) {
   369  		if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, cluster); err != nil {
   370  			return nil, err
   371  		}
   372  	}
   373  
   374  	for _, repoUrl := range difference(q.Project.Spec.SourceRepos, oldProj.Spec.SourceRepos) {
   375  		if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionUpdate, repoUrl); err != nil {
   376  			return nil, err
   377  		}
   378  	}
   379  
   380  	clusterResourceWhitelistsEqual := reflect.DeepEqual(q.Project.Spec.ClusterResourceWhitelist, oldProj.Spec.ClusterResourceWhitelist)
   381  	clusterResourceBlacklistsEqual := reflect.DeepEqual(q.Project.Spec.ClusterResourceBlacklist, oldProj.Spec.ClusterResourceBlacklist)
   382  	namespacesResourceBlacklistsEqual := reflect.DeepEqual(q.Project.Spec.NamespaceResourceBlacklist, oldProj.Spec.NamespaceResourceBlacklist)
   383  	namespacesResourceWhitelistsEqual := reflect.DeepEqual(q.Project.Spec.NamespaceResourceWhitelist, oldProj.Spec.NamespaceResourceWhitelist)
   384  	if !clusterResourceWhitelistsEqual || !clusterResourceBlacklistsEqual || !namespacesResourceBlacklistsEqual || !namespacesResourceWhitelistsEqual {
   385  		for _, cluster := range q.Project.Spec.DestinationClusters() {
   386  			if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, cluster); err != nil {
   387  				return nil, err
   388  			}
   389  		}
   390  	}
   391  
   392  	appsList, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).List(ctx, metav1.ListOptions{})
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  
   397  	var srcValidatedApps []v1alpha1.Application
   398  	var dstValidatedApps []v1alpha1.Application
   399  	getProjectClusters := func(project string) ([]*v1alpha1.Cluster, error) {
   400  		return s.db.GetProjectClusters(ctx, project)
   401  	}
   402  
   403  	for _, a := range argo.FilterByProjects(appsList.Items, []string{q.Project.Name}) {
   404  		if oldProj.IsSourcePermitted(a.Spec.GetSource()) {
   405  			srcValidatedApps = append(srcValidatedApps, a)
   406  		}
   407  
   408  		dstPermitted, err := oldProj.IsDestinationPermitted(a.Spec.Destination, getProjectClusters)
   409  		if err != nil {
   410  			return nil, err
   411  		}
   412  
   413  		if dstPermitted {
   414  			dstValidatedApps = append(dstValidatedApps, a)
   415  		}
   416  	}
   417  
   418  	invalidSrcCount := 0
   419  	invalidDstCount := 0
   420  
   421  	for _, a := range srcValidatedApps {
   422  		if !q.Project.IsSourcePermitted(a.Spec.GetSource()) {
   423  			invalidSrcCount++
   424  		}
   425  	}
   426  	for _, a := range dstValidatedApps {
   427  		dstPermitted, err := q.Project.IsDestinationPermitted(a.Spec.Destination, getProjectClusters)
   428  		if err != nil {
   429  			return nil, err
   430  		}
   431  
   432  		if !dstPermitted {
   433  			invalidDstCount++
   434  		}
   435  	}
   436  
   437  	var parts []string
   438  	if invalidSrcCount > 0 {
   439  		parts = append(parts, fmt.Sprintf("%d applications source became invalid", invalidSrcCount))
   440  	}
   441  	if invalidDstCount > 0 {
   442  		parts = append(parts, fmt.Sprintf("%d applications destination became invalid", invalidDstCount))
   443  	}
   444  	if len(parts) > 0 {
   445  		return nil, status.Errorf(codes.InvalidArgument, "as a result of project update %s", strings.Join(parts, " and "))
   446  	}
   447  
   448  	res, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(ctx, q.Project, metav1.UpdateOptions{})
   449  	if err == nil {
   450  		s.logEvent(res, ctx, argo.EventReasonResourceUpdated, "updated project")
   451  	}
   452  	return res, err
   453  }
   454  
   455  // Delete deletes a project
   456  func (s *Server) Delete(ctx context.Context, q *project.ProjectQuery) (*project.EmptyResponse, error) {
   457  	if q.Name == v1alpha1.DefaultAppProjectName {
   458  		return nil, status.Errorf(codes.InvalidArgument, "name '%s' is reserved and cannot be deleted", q.Name)
   459  	}
   460  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionDelete, q.Name); err != nil {
   461  		return nil, err
   462  	}
   463  
   464  	s.projectLock.Lock(q.Name)
   465  	defer s.projectLock.Unlock(q.Name)
   466  
   467  	p, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Name, metav1.GetOptions{})
   468  	if err != nil {
   469  		return nil, err
   470  	}
   471  
   472  	appsList, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).List(ctx, metav1.ListOptions{})
   473  	if err != nil {
   474  		return nil, err
   475  	}
   476  	apps := argo.FilterByProjects(appsList.Items, []string{q.Name})
   477  	if len(apps) > 0 {
   478  		return nil, status.Errorf(codes.InvalidArgument, "project is referenced by %d applications", len(apps))
   479  	}
   480  	err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Delete(ctx, q.Name, metav1.DeleteOptions{})
   481  	if err == nil {
   482  		s.logEvent(p, ctx, argo.EventReasonResourceDeleted, "deleted project")
   483  	}
   484  	return &project.EmptyResponse{}, err
   485  }
   486  
   487  func (s *Server) ListEvents(ctx context.Context, q *project.ProjectQuery) (*v1.EventList, error) {
   488  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionGet, q.Name); err != nil {
   489  		return nil, err
   490  	}
   491  	proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Name, metav1.GetOptions{})
   492  	if err != nil {
   493  		return nil, err
   494  	}
   495  	fieldSelector := fields.SelectorFromSet(map[string]string{
   496  		"involvedObject.name":      proj.Name,
   497  		"involvedObject.uid":       string(proj.UID),
   498  		"involvedObject.namespace": proj.Namespace,
   499  	}).String()
   500  	return s.kubeclientset.CoreV1().Events(s.ns).List(ctx, metav1.ListOptions{FieldSelector: fieldSelector})
   501  }
   502  
   503  func (s *Server) logEvent(a *v1alpha1.AppProject, ctx context.Context, reason string, action string) {
   504  	eventInfo := argo.EventInfo{Type: v1.EventTypeNormal, Reason: reason}
   505  	user := session.Username(ctx)
   506  	if user == "" {
   507  		user = "Unknown user"
   508  	}
   509  	message := fmt.Sprintf("%s %s", user, action)
   510  	s.auditLogger.LogAppProjEvent(a, eventInfo, message, user)
   511  }
   512  
   513  func (s *Server) GetSyncWindowsState(ctx context.Context, q *project.SyncWindowsQuery) (*project.SyncWindowsResponse, error) {
   514  	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionGet, q.Name); err != nil {
   515  		return nil, err
   516  	}
   517  	proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Name, metav1.GetOptions{})
   518  
   519  	if err != nil {
   520  		return nil, err
   521  	}
   522  
   523  	res := &project.SyncWindowsResponse{}
   524  
   525  	windows := proj.Spec.SyncWindows.Active()
   526  	if windows.HasWindows() {
   527  		res.Windows = *windows
   528  	} else {
   529  		res.Windows = []*v1alpha1.SyncWindow{}
   530  	}
   531  
   532  	return res, nil
   533  }
   534  
   535  func (s *Server) NormalizeProjs() error {
   536  	projList, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).List(context.Background(), metav1.ListOptions{})
   537  	if err != nil {
   538  		return status.Errorf(codes.Internal, "Error retrieving project list: %s", err.Error())
   539  	}
   540  	for _, proj := range projList.Items {
   541  		for i := 0; i < 3; i++ {
   542  			if proj.NormalizeJWTTokens() {
   543  				_, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(context.Background(), &proj, metav1.UpdateOptions{})
   544  				if err == nil {
   545  					log.Infof("Successfully normalized project %s.", proj.Name)
   546  					break
   547  				}
   548  				if !apierr.IsConflict(err) {
   549  					log.Warnf("Failed normalize project %s", proj.Name)
   550  					break
   551  				}
   552  				projGet, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(context.Background(), proj.Name, metav1.GetOptions{})
   553  				if err != nil {
   554  					return status.Errorf(codes.Internal, "Error retrieving project: %s", err.Error())
   555  				}
   556  				proj = *projGet
   557  				if i == 2 {
   558  					return status.Errorf(codes.Internal, "Failed normalize project %s", proj.Name)
   559  				}
   560  			} else {
   561  				break
   562  			}
   563  		}
   564  	}
   565  	return nil
   566  }