3333import com .google .solutions .jitaccess .auth .IamPrincipalId ;
3434import com .google .solutions .jitaccess .common .Coalesce ;
3535import jakarta .inject .Singleton ;
36+ import org .crac .Resource ;
3637import org .jetbrains .annotations .NotNull ;
38+ import org .jetbrains .annotations .Nullable ;
3739
3840import java .io .IOException ;
3941import java .time .Instant ;
@@ -61,8 +63,8 @@ public class CloudIdentityGroupsClient {
6163 private final @ NotNull HttpTransport .Options httpOptions ;
6264
6365 /**
64- * Settings for new groups :
65- * <p>
66+ * Default, restrictive access settings :
67+ *
6668 * - Allow external members.
6769 * - Disable most self-service features on groups.google.com to
6870 * the extent possible.
@@ -87,6 +89,12 @@ public class CloudIdentityGroupsClient {
8789 .setWhoCanViewGroup ("ALL_MANAGERS_CAN_VIEW" )
8890 .setWhoCanViewMembership ("ALL_MANAGERS_CAN_VIEW" );
8991
92+ /**
93+ * GKE compatible access settings.
94+ */
95+ private final @ NotNull Groups GKE_COMPATIBLE = RESTRICTED_SETTINGS
96+ .setWhoCanViewMembership ("ALL_MEMBERS_CAN_VIEW" );
97+
9098 public CloudIdentityGroupsClient (
9199 @ NotNull GoogleCredentials credentials ,
92100 @ NotNull Options options ,
@@ -148,21 +156,27 @@ private static void translateAndThrowApiException(
148156 }
149157 }
150158
151-
152159 /**
153160 * Update group settings to restrictive defaults.
154161 */
155- private void restrictGroupSettings (@ NotNull GroupId emailAddress ) throws IOException {
162+ private void setGroupAccess (
163+ @ NotNull GroupId emailAddress ,
164+ @ NotNull AccessProfile profile
165+ ) throws IOException {
156166 var settingsClient = createSettingsClient ();
157167
168+ var settings = profile == AccessProfile .GkeCompatible
169+ ? this .GKE_COMPATIBLE
170+ : this .RESTRICTED_SETTINGS ;
171+
158172 //
159173 // The group settings API is prone to fail for newly created groups.
160174 //
161175 for (int attempt = 0 ; attempt < MAX_GROUP_SETTINGS_PATCH_ATTEMPTS ; attempt ++) {
162176 try {
163177 settingsClient
164178 .groups ()
165- .update (emailAddress .email , this . RESTRICTED_SETTINGS )
179+ .update (emailAddress .email , settings )
166180 .execute ();
167181
168182 //
@@ -271,29 +285,15 @@ private void restrictGroupSettings(@NotNull GroupId emailAddress) throws IOExcep
271285 return lookupGroup (createClient (), groupId );
272286 }
273287
274- public enum GroupType {
275- /**
276- * Normal group. Creating this type of group doesn't require special
277- * privileges.
278- */
279- DiscussionForum ,
280-
281- /**
282- * Security group. Creating this type of group requires the 'Groups Admin'
283- * admin role, or an equivalent custom role that has the privilege to
284- * assign security labels.
285- */
286- Security
287- }
288-
289288 /**
290289 * Create group in an idempotent way.
291290 */
292291 public @ NotNull GroupKey createGroup (
293292 @ NotNull GroupId emailAddress ,
294293 @ NotNull GroupType type ,
295294 @ NotNull String displayName ,
296- @ NotNull String description
295+ @ NotNull String description ,
296+ @ NotNull AccessProfile accessProfile
297297 ) throws AccessException , IOException {
298298 try {
299299 var labels = new HashMap <String , String >();
@@ -372,7 +372,7 @@ public enum GroupType {
372372 //
373373 // Lock down group settings.
374374 //
375- restrictGroupSettings (emailAddress );
375+ setGroupAccess (emailAddress , accessProfile );
376376
377377 return groupKey ;
378378 }
@@ -515,11 +515,12 @@ public void deleteGroup(
515515 /**
516516 * Delete a group membership in an idempotent way.
517517 */
518- public void deleteMembership (
518+ private void deleteMembership (
519+ @ NotNull CloudIdentity client ,
519520 @ NotNull MembershipId membershipId
520521 ) throws AccessException , IOException {
521522 try {
522- createClient ()
523+ client
523524 .groups ()
524525 .memberships ()
525526 .delete (membershipId .id )
@@ -537,6 +538,46 @@ public void deleteMembership(
537538 }
538539 }
539540
541+ /**
542+ * Delete a group membership in an idempotent way.
543+ */
544+ public void deleteMembership (
545+ @ NotNull MembershipId membershipId
546+ ) throws AccessException , IOException {
547+ deleteMembership (createClient (), membershipId );
548+ }
549+
550+ /**
551+ * Delete a group membership in an idempotent way.
552+ */
553+ public void deleteMembership (
554+ @ NotNull GroupKey groupKey ,
555+ @ NotNull IamPrincipalId member
556+ ) throws AccessException , IOException {
557+ var client = createClient ();
558+
559+ //
560+ // Lookup membership, assuming it exists.
561+ //
562+ MembershipId membershipId ;
563+ try
564+ {
565+ membershipId = lookupGroupMembership (
566+ client ,
567+ groupKey ,
568+ member );
569+ }
570+ catch (AccessException e )
571+ {
572+ //
573+ // Membership doesn't exist, so there's nothing to delete.
574+ //
575+ return ;
576+ }
577+
578+ deleteMembership (client , membershipId );
579+ }
580+
540581 private @ NotNull MembershipId updateMembership (
541582 @ NotNull CloudIdentity client ,
542583 @ NotNull GroupKey groupKey ,
@@ -572,15 +613,18 @@ public void deleteMembership(
572613 @ NotNull CloudIdentity client ,
573614 @ NotNull GroupKey groupKey ,
574615 @ NotNull IamPrincipalId member ,
575- @ NotNull Instant expiry
616+ @ Nullable Instant expiry
576617 ) throws AccessException , IOException {
577618 var role = new MembershipRole ()
578- .setName ("MEMBER" )
579- .setExpiryDetail (new ExpiryDetail ()
619+ .setName ("MEMBER" );
620+
621+ if (expiry != null ) {
622+ role .setExpiryDetail (new ExpiryDetail ()
580623 .setExpireTime (expiry
581624 .atOffset (ZoneOffset .UTC )
582625 .truncatedTo (ChronoUnit .SECONDS )
583626 .format (DateTimeFormatter .ISO_DATE_TIME )));
627+ }
584628
585629 try {
586630 //
@@ -619,6 +663,31 @@ public void deleteMembership(
619663 }
620664 }
621665
666+ /**
667+ * Permanently add a member to a group in an idempotent way.
668+ */
669+ public @ NotNull MembershipId addPermanentMembership (
670+ @ NotNull GroupKey groupKey ,
671+ @ NotNull IamPrincipalId member
672+ ) throws AccessException , IOException {
673+ return addMembership (createClient (), groupKey , member , null );
674+ }
675+
676+ /**
677+ * Permanently add a member to a group in an idempotent way.
678+ */
679+ public @ NotNull MembershipId addPermanentMembership (
680+ @ NotNull GroupId groupId ,
681+ @ NotNull IamPrincipalId member
682+ ) throws AccessException , IOException {
683+ var client = createClient ();
684+ return addMembership (
685+ client ,
686+ lookupGroup (client , groupId ),
687+ member ,
688+ null );
689+ }
690+
622691 /**
623692 * Add a member to a group in an idempotent way.
624693 */
@@ -627,6 +696,7 @@ public void deleteMembership(
627696 @ NotNull IamPrincipalId member ,
628697 @ NotNull Instant expiry
629698 ) throws AccessException , IOException {
699+ Preconditions .checkNotNull (expiry , "expiry" );
630700 return addMembership (createClient (), groupKey , member , expiry );
631701 }
632702
@@ -638,6 +708,8 @@ public void deleteMembership(
638708 @ NotNull IamPrincipalId member ,
639709 @ NotNull Instant expiry
640710 ) throws AccessException , IOException {
711+ Preconditions .checkNotNull (expiry , "expiry" );
712+
641713 var client = createClient ();
642714 return addMembership (
643715 client ,
@@ -833,9 +905,36 @@ public void deleteMembership(
833905 }
834906
835907 //---------------------------------------------------------------------------
836- // Inner classes .
908+ // Inner types .
837909 //---------------------------------------------------------------------------
838910
911+ public enum GroupType {
912+ /**
913+ * Normal group. Creating this type of group doesn't require special
914+ * privileges.
915+ */
916+ DiscussionForum ,
917+
918+ /**
919+ * Security group. Creating this type of group requires the 'Groups Admin'
920+ * admin role, or an equivalent custom role that has the privilege to
921+ * assign security labels.
922+ */
923+ Security
924+ }
925+
926+ public enum AccessProfile {
927+ /**
928+ * Use restrictive access settings.
929+ */
930+ Restricted ,
931+
932+ /**
933+ * Use access settings that are restrictive, but still compatible GKE RBAC.
934+ */
935+ GkeCompatible
936+ }
937+
839938 public record MembershipId (String id ) {}
840939
841940 public record Options (
0 commit comments