From f6c8f742b303b118814a3544e8221cbf1da7c557 Mon Sep 17 00:00:00 2001 From: Josh Freda Date: Thu, 11 Apr 2024 16:59:38 -0500 Subject: [PATCH 1/8] Update group approvals config format --- configs/config.hcl | 11 +++++++++-- internal/config/config.go | 15 +++++++++++++-- web/web.go | 9 +++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/configs/config.hcl b/configs/config.hcl index 0f9f2630d..f2b40b5a7 100644 --- a/configs/config.hcl +++ b/configs/config.hcl @@ -122,8 +122,15 @@ google_workspace { // drafts_folder contains all draft documents. drafts_folder = "my-drafts-folder-id" - // groups_prefix is the prefix to use when searching for Google Groups. - // groups_prefix = "team-" + // group_approvals is the configuration for using Google Groups as document + // approvers. + group_approvals { + // enabled enables using Google Groups as document approvers. + enabled = false + + // search_prefix is the prefix to use when searching for Google Groups. + // search_prefix = "team-" + } // If create_doc_shortcuts is set to true, shortcuts_folder will contain an // organized hierarchy of folders and shortcuts to published files that can be diff --git a/internal/config/config.go b/internal/config/config.go index 9144e8f48..29e3a3ced 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -219,8 +219,9 @@ type GoogleWorkspace struct { // DraftsFolder is the folder that contains all document drafts. DraftsFolder string `hcl:"drafts_folder"` - // GroupsPrefix is the prefix to use when searching for Google Groups. - GroupsPrefix string `hcl:"groups_prefix,optional"` + // GoogleWorkspaceGroupApprovals is the configuration for using Google Groups as + // document approvers. + GroupApprovals *GoogleWorkspaceGroupApprovals `hcl:"group_approvals,block"` // OAuth2 is the configuration to use OAuth 2.0 to access Google Workspace // APIs. @@ -241,6 +242,16 @@ type GoogleWorkspace struct { UserNotFoundEmail *GoogleWorkspaceUserNotFoundEmail `hcl:"user_not_found_email,block"` } +// GoogleWorkspaceGroupApprovals is the configuration for using Google Groups as +// document approvers. +type GoogleWorkspaceGroupApprovals struct { + // Enabled enables using Google Groups as document approvers. + Enabled bool `hcl:"enabled,optional"` + + // SearchPrefix is the prefix to use when searching for Google Groups. + SearchPrefix string `hcl:"search_prefix,optional"` +} + // GoogleWorkspaceOAuth2 is the configuration to use OAuth 2.0 to access Google // Workspace APIs. type GoogleWorkspaceOAuth2 struct { diff --git a/web/web.go b/web/web.go index 363a8fc63..c8a18e199 100644 --- a/web/web.go +++ b/web/web.go @@ -64,6 +64,7 @@ type ConfigResponse struct { GoogleAnalyticsTagID string `json:"google_analytics_tag_id"` GoogleOAuth2ClientID string `json:"google_oauth2_client_id"` GoogleOAuth2HD string `json:"google_oauth2_hd"` + GroupApprovals bool `json:"group_approvals"` JiraURL string `json:"jira_url"` ShortLinkBaseURL string `json:"short_link_base_url"` SkipGoogleAuth bool `json:"skip_google_auth"` @@ -120,6 +121,13 @@ func ConfigHandler( createDocsAsUser = true } + // Set GroupApprovals if enabled in the config. + groupApprovals := false + if cfg.GoogleWorkspace.GroupApprovals != nil && + cfg.GoogleWorkspace.GroupApprovals.Enabled { + groupApprovals = true + } + // Set JiraURL if enabled in the config. jiraURL := "" if cfg.Jira != nil && cfg.Jira.Enabled { @@ -136,6 +144,7 @@ func ConfigHandler( GoogleAnalyticsTagID: cfg.GoogleAnalyticsTagID, GoogleOAuth2ClientID: cfg.GoogleWorkspace.OAuth2.ClientID, GoogleOAuth2HD: cfg.GoogleWorkspace.OAuth2.HD, + GroupApprovals: groupApprovals, JiraURL: jiraURL, ShortLinkBaseURL: shortLinkBaseURL, SkipGoogleAuth: skipGoogleAuth, From 0ea3d9e0908ee94123632ebb678b480b9ddd2e33 Mon Sep 17 00:00:00 2001 From: Josh Freda Date: Thu, 11 Apr 2024 17:00:17 -0500 Subject: [PATCH 2/8] Update docs to account for group approvals --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0b99662bf..7f7a0ebdb 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Hermes was created and is currently maintained by HashiCorp Labs, a small team i 1. Enable the following APIs for [Google Workspace APIs](https://developers.google.com/workspace/guides/enable-apis) - - Admin SDK API + - Admin SDK API (optional, if enabling Google Groups as document approvers) - Google Docs API - Google Drive API - Gmail API @@ -146,12 +146,12 @@ NOTE: when not using a Google service account, this will automatically open a br - Create a new key (JSON type) for the service account and download it. - Go to [Delegating domain-wide authority to the service account](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority) and follow the instructions to enter the OAuth scopes. -- Add the following OAuth scopes (comma-delimited list): +- Add the following OAuth scopes (if enabling group approvals, add `https://www.googleapis.com/auth/admin.directory.group.readonly` to the comma-delimited list): `https://www.googleapis.com/auth/directory.readonly,https://www.googleapis.com/auth/documents,https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/gmail.send` 1. Configure the service account in the `auth` block under the `google_workspace` config block. -More to come here... +1. If enabling group approvals, add the `https://www.googleapis.com/auth/admin.directory.group.readonly` role to the service user configured as the `subject` in the `auth` block (from previous step). ## Architecture From 3b447b0a69c0e0fc9c3179e1fad9e4f2ef367252 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Fri, 12 Apr 2024 09:45:23 -0400 Subject: [PATCH 3/8] Add property to Config; update API --- internal/api/v2/groups.go | 4 ++-- web/app/config/environment.d.ts | 1 + web/app/services/config.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/api/v2/groups.go b/internal/api/v2/groups.go index 1d36c6d74..b40b22940 100644 --- a/internal/api/v2/groups.go +++ b/internal/api/v2/groups.go @@ -73,11 +73,11 @@ func GroupsHandler(srv server.Server) http.Handler { ) // Retrieve groups with prefix, if configured. - if srv.Config.GoogleWorkspace.GroupsPrefix != "" { + if srv.Config.GoogleWorkspace.GroupApprovals.SearchPrefix != "" { maxNonPrefixGroups = maxGroupResults - maxPrefixGroupResults prefixQuery := fmt.Sprintf( - "%s%s", srv.Config.GoogleWorkspace.GroupsPrefix, query) + "%s%s", srv.Config.GoogleWorkspace.GroupApprovals.SearchPrefix, query) prefixGroups, err = srv.GWService.AdminDirectory.Groups.List(). Domain(srv.Config.GoogleWorkspace.Domain). MaxResults(maxPrefixGroupResults). diff --git a/web/app/config/environment.d.ts b/web/app/config/environment.d.ts index ee22fd178..0db17b7dd 100644 --- a/web/app/config/environment.d.ts +++ b/web/app/config/environment.d.ts @@ -24,6 +24,7 @@ export interface HermesConfig { }; shortLinkBaseURL: string; skipGoogleAuth: boolean; + groupApprovals: boolean; showEmberAnimatedTools: boolean; supportLinkURL: string; version: string; diff --git a/web/app/services/config.ts b/web/app/services/config.ts index 454bfca7f..3226908b9 100644 --- a/web/app/services/config.ts +++ b/web/app/services/config.ts @@ -18,6 +18,7 @@ export default class ConfigService extends Service { support_link_url: config.supportLinkURL, version: config.version, short_revision: config.shortRevision, + group_approvals: config.groupApprovals, }; setConfig(param: HermesConfig) { From 2fa655ba324b0ba68b5c3e282be55fe00e234381 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Fri, 12 Apr 2024 09:54:46 -0400 Subject: [PATCH 4/8] Make approver groups conditional --- web/app/components/document/sidebar.hbs | 4 ++-- web/app/routes/authenticated/document.ts | 24 +++++++++++++----------- web/mirage/utils.ts | 1 + 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/web/app/components/document/sidebar.hbs b/web/app/components/document/sidebar.hbs index 21569e3a2..d7ec892f2 100644 --- a/web/app/components/document/sidebar.hbs +++ b/web/app/components/document/sidebar.hbs @@ -289,7 +289,7 @@ @onSave={{perform this.saveApprovers}} @isSaving={{this.saveIsRunning}} @isReadOnly={{this.editingIsDisabled}} - @includeGroupsInPeopleSelect={{true}} + @includeGroupsInPeopleSelect={{this.configSvc.config.group_approvals}} {{! Provide the document to the `has-approved-doc` helper }} @document={{@document}} /> @@ -767,7 +767,7 @@ r); - - const allowed = resp?.headers.get("allowed"); - - if (allowed?.includes("POST")) { - viewerIsGroupApprover = true; + if (this.configSvc.config.group_approvals) { + const resp = await this.fetchSvc + .fetch( + `/api/${this.configSvc.config.api_version}/approvals/${params.document_id}`, + { method: "OPTIONS" }, + ) + .then((r) => r); + + const allowed = resp?.headers.get("allowed"); + + if (allowed?.includes("POST")) { + viewerIsGroupApprover = true; + } } const typedDoc = doc as HermesDocument; diff --git a/web/mirage/utils.ts b/web/mirage/utils.ts index 5bd4ed332..128e3a3c6 100644 --- a/web/mirage/utils.ts +++ b/web/mirage/utils.ts @@ -50,6 +50,7 @@ export const TEST_WEB_CONFIG = { google_doc_folders: "", short_link_base_url: TEST_SHORT_LINK_BASE_URL, skip_google_auth: false, + group_approvals: true, google_analytics_tag_id: undefined, support_link_url: TEST_SUPPORT_URL, version: "1.2.3", From 2bc1ab51af3af38d37cb29005ac94a117e6e9bd4 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Fri, 12 Apr 2024 10:55:09 -0400 Subject: [PATCH 5/8] Add dashboard announcement --- .../dashboard/new-features-banner.hbs | 34 ++++++++++++++++--- .../dashboard/new-features-banner.ts | 2 +- web/app/styles/components/dashboard.scss | 15 ++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/web/app/components/dashboard/new-features-banner.hbs b/web/app/components/dashboard/new-features-banner.hbs index 339a81d8d..d0e69e465 100644 --- a/web/app/components/dashboard/new-features-banner.hbs +++ b/web/app/components/dashboard/new-features-banner.hbs @@ -3,15 +3,39 @@ data-test-new-features-banner @type="inline" @color="highlight" - @icon="folder-star" + {{! Icon is hidden by CSS; See `dashboard.scss` }} @onDismiss={{this.dismiss}} class="mb-10" as |A| > - Introducing Projects! + What's new in Hermes - Projects are a new way to organize documents and links around an effort. -
+
    + {{! Conditional }} +
  • + +

    + Google Groups can be added as document approvers +

    +
  • +
  • + +

    + Document ownership can be transferred between users +

    +
  • +
  • + +

    + We've improved owner filtering on the + + All Docs + + view +

    +
  • +
+ {{!
-
+
}}
{{/if}} diff --git a/web/app/components/dashboard/new-features-banner.ts b/web/app/components/dashboard/new-features-banner.ts index dc0d8a54e..31be59ed0 100644 --- a/web/app/components/dashboard/new-features-banner.ts +++ b/web/app/components/dashboard/new-features-banner.ts @@ -4,7 +4,7 @@ import window from "ember-window-mock"; import { action } from "@ember/object"; export const NEW_FEATURES_BANNER_LOCAL_STORAGE_ITEM = - "jan-18-2024-newFeatureBannerIsShown"; + "apr-12-2024-newFeatureBannerIsShown"; interface DashboardNewFeaturesBannerSignature { Args: {}; diff --git a/web/app/styles/components/dashboard.scss b/web/app/styles/components/dashboard.scss index 9becc6679..5250f4343 100644 --- a/web/app/styles/components/dashboard.scss +++ b/web/app/styles/components/dashboard.scss @@ -68,3 +68,18 @@ @apply mt-0; } } + +.hds-alert--color-highlight { + @apply pl-5; + + .hds-alert__icon { + @apply hidden; + } +} + +.icon-list { + li { + @apply grid items-center gap-2.5 py-0.5 pl-1; + grid-template-columns: 16px 1fr; + } +} From f8c1c75e0b950f670a48e49287fed6d519e3012b Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Fri, 12 Apr 2024 11:05:16 -0400 Subject: [PATCH 6/8] Make Group callout conditional --- .../components/dashboard/new-features-banner.hbs | 15 ++++++++------- .../components/dashboard/new-features-banner.ts | 7 +++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/web/app/components/dashboard/new-features-banner.hbs b/web/app/components/dashboard/new-features-banner.hbs index d0e69e465..34216aa00 100644 --- a/web/app/components/dashboard/new-features-banner.hbs +++ b/web/app/components/dashboard/new-features-banner.hbs @@ -11,13 +11,14 @@ What's new in Hermes
    - {{! Conditional }} -
  • - -

    - Google Groups can be added as document approvers -

    -
  • + {{#if this.configSvc.config.group_approvals}} +
  • + +

    + Google Groups can be added as document approvers +

    +
  • + {{/if}}
  • diff --git a/web/app/components/dashboard/new-features-banner.ts b/web/app/components/dashboard/new-features-banner.ts index 31be59ed0..f9e18de35 100644 --- a/web/app/components/dashboard/new-features-banner.ts +++ b/web/app/components/dashboard/new-features-banner.ts @@ -2,6 +2,8 @@ import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; import window from "ember-window-mock"; import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import ConfigService from "hermes/services/config"; export const NEW_FEATURES_BANNER_LOCAL_STORAGE_ITEM = "apr-12-2024-newFeatureBannerIsShown"; @@ -11,6 +13,11 @@ interface DashboardNewFeaturesBannerSignature { } export default class DashboardNewFeaturesBanner extends Component { + /** + * Used to determine whether the Google Groups callout should be shown. + */ + @service("config") declare configSvc: ConfigService; + @tracked protected isDismissed = false; /** From cee9681c3d9dbbfab3b7e098e0359f35137c5272 Mon Sep 17 00:00:00 2001 From: Josh Freda Date: Fri, 12 Apr 2024 11:24:39 -0500 Subject: [PATCH 7/8] Update groups API --- internal/api/v2/groups.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/api/v2/groups.go b/internal/api/v2/groups.go index b40b22940..ce0bd2988 100644 --- a/internal/api/v2/groups.go +++ b/internal/api/v2/groups.go @@ -47,6 +47,14 @@ func GroupsHandler(srv server.Server) http.Handler { return } + // Respond with error if group approvals are not enabled. + if srv.Config.GoogleWorkspace.GroupApprovals == nil || + !srv.Config.GoogleWorkspace.GroupApprovals.Enabled { + http.Error(w, + "Group approvals have not been enabled", http.StatusUnprocessableEntity) + return + } + switch r.Method { case "POST": // Decode request. @@ -73,11 +81,16 @@ func GroupsHandler(srv server.Server) http.Handler { ) // Retrieve groups with prefix, if configured. - if srv.Config.GoogleWorkspace.GroupApprovals.SearchPrefix != "" { + searchPrefix := "" + if srv.Config.GoogleWorkspace.GroupApprovals != nil && + srv.Config.GoogleWorkspace.GroupApprovals.SearchPrefix != "" { + searchPrefix = srv.Config.GoogleWorkspace.GroupApprovals.SearchPrefix + } + if searchPrefix != "" { maxNonPrefixGroups = maxGroupResults - maxPrefixGroupResults prefixQuery := fmt.Sprintf( - "%s%s", srv.Config.GoogleWorkspace.GroupApprovals.SearchPrefix, query) + "%s%s", searchPrefix, query) prefixGroups, err = srv.GWService.AdminDirectory.Groups.List(). Domain(srv.Config.GoogleWorkspace.Domain). MaxResults(maxPrefixGroupResults). From 58dad4f80ec95803e0e83c141e0652c39a571ab4 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Fri, 12 Apr 2024 12:42:35 -0400 Subject: [PATCH 8/8] Remove commented-out code --- .../components/dashboard/new-features-banner.hbs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/web/app/components/dashboard/new-features-banner.hbs b/web/app/components/dashboard/new-features-banner.hbs index 34216aa00..de1ebe51b 100644 --- a/web/app/components/dashboard/new-features-banner.hbs +++ b/web/app/components/dashboard/new-features-banner.hbs @@ -36,20 +36,6 @@

- {{!
- - or - -
}}
{{/if}}