@@ -153,6 +153,28 @@ class Auth:
153
153
KEY_JWT_TOKEN = "jwt_token"
154
154
KEY_ALT_JWT_TOKEN = "JWT_TOKEN"
155
155
156
+ # The various prefixes that can be used in Catalog ACLs.
157
+ ACL_PREFIX_USER = "user:" # Followed by the user's sha1 hash
158
+ ACL_PREFIX_EMAIL = "email:" # Followed by the user's email
159
+ ACL_PREFIX_GROUP = "group:" # Followed by a lowercase group
160
+ ACL_PREFIX_ORG = "org:" # Followed by a lowercase org name
161
+ ACL_PREFIX_ACCESS = "access-id:" # Followed by the purchase-specific access id
162
+ # Note that the access-id, including the prefix `access_id:`, is matched against
163
+ # a group with the same name. In other words `group:access-id:<access-id>` will
164
+ # match against `access-id:<access-id>` (assuming the `<access_id>` is identical).
165
+
166
+ # these match the values in descarteslabs/common/services/python_auth/groups.py
167
+ ORG_ADMIN_SUFFIX = ":org-admin"
168
+ RESOURCE_ADMIN_SUFFIX = ":resource-admin"
169
+
170
+ # These are cache keys for caching various data in the object's __dict__.
171
+ # These are scrubbed out with `_clear_cache()` when retrieving a new token.
172
+ KEY_PAYLOAD = "_payload"
173
+ KEY_ALL_ACL_SUBJECTS = "_aas"
174
+ KEY_ALL_ACL_SUBJECTS_AS_SET = "_aasas"
175
+ KEY_ALL_OWNER_ACL_SUBJECTS = "_aoas"
176
+ KEY_ALL_OWNER_ACL_SUBJECTS_AS_SET = "_aoasas"
177
+
156
178
__attrs__ = [
157
179
"domain" ,
158
180
"scope" ,
@@ -585,7 +607,13 @@ def payload(self):
585
607
OauthError
586
608
Raised when a token cannot be obtained or refreshed.
587
609
"""
588
- return self ._get_payload (self .token )
610
+ payload = self .__dict__ .get (self .KEY_PAYLOAD )
611
+
612
+ if payload is None :
613
+ payload = self ._get_payload (self .token )
614
+ self .__dict__ [self .KEY_PAYLOAD ] = payload
615
+
616
+ return payload
589
617
590
618
@staticmethod
591
619
def _get_payload (token ):
@@ -754,6 +782,9 @@ def _get_token(self, timeout=100):
754
782
else :
755
783
raise OauthError ("Could not retrieve token" )
756
784
785
+ # clear out payload and subjects cache
786
+ self ._clear_cache ()
787
+
757
788
token_info = {}
758
789
759
790
# Read the token from the token_info_path, and save it again
@@ -797,6 +828,121 @@ def namespace(self):
797
828
self ._namespace = sha1 (self .payload ["sub" ].encode ("utf-8" )).hexdigest ()
798
829
return self ._namespace
799
830
831
+ @property
832
+ def all_acl_subjects (self ):
833
+ """
834
+ A list of all ACL subjects identifying this user (the user itself, the org, the
835
+ groups) which can be used in ACL queries.
836
+ """
837
+ subjects = self .__dict__ .get (self .KEY_ALL_ACL_SUBJECTS )
838
+
839
+ if subjects is None :
840
+ subjects = [self .ACL_PREFIX_USER + self .namespace ]
841
+
842
+ if email := self .payload .get ("email" ):
843
+ subjects .append (self .ACL_PREFIX_EMAIL + email .lower ())
844
+
845
+ if org := self .payload .get ("org" ):
846
+ subjects .append (self .ACL_PREFIX_ORG + org )
847
+
848
+ subjects += [
849
+ self .ACL_PREFIX_GROUP + group for group in self ._active_groups ()
850
+ ]
851
+ self .__dict__ [self .KEY_ALL_ACL_SUBJECTS ] = subjects
852
+
853
+ return subjects
854
+
855
+ @property
856
+ def all_acl_subjects_as_set (self ):
857
+ subjects_as_set = self .__dict__ .get (self .KEY_ALL_ACL_SUBJECTS_AS_SET )
858
+
859
+ if subjects_as_set is None :
860
+ subjects_as_set = set (self .all_acl_subjects )
861
+ self .__dict__ [self .KEY_ALL_ACL_SUBJECTS_AS_SET ] = subjects_as_set
862
+
863
+ return subjects_as_set
864
+
865
+ @property
866
+ def all_owner_acl_subjects (self ):
867
+ """
868
+ A list of ACL subjects identifying this user (the user itself, the org,
869
+ org admin and catalog admins) which can be used in owner ACL queries.
870
+ """
871
+ subjects = self .__dict__ .get (self .KEY_ALL_OWNER_ACL_SUBJECTS )
872
+
873
+ if subjects is None :
874
+ subjects = [self .ACL_PREFIX_USER + self .namespace ]
875
+
876
+ subjects .extend (
877
+ [self .ACL_PREFIX_ORG + org for org in self .get_org_admins () if org ]
878
+ )
879
+ subjects .extend (
880
+ [
881
+ self .ACL_PREFIX_ACCESS + access_id
882
+ for access_id in self .get_resource_admins ()
883
+ if access_id
884
+ ]
885
+ )
886
+ self .__dict__ [self .KEY_ALL_OWNER_ACL_SUBJECTS ] = subjects
887
+
888
+ return subjects
889
+
890
+ @property
891
+ def all_owner_acl_subjects_as_set (self ):
892
+ subjects_as_set = self .__dict__ .get (self .KEY_ALL_OWNER_ACL_SUBJECTS_AS_SET )
893
+
894
+ if subjects_as_set is None :
895
+ subjects_as_set = set (self .all_owner_acl_subjects )
896
+ self .__dict__ [self .KEY_ALL_OWNER_ACL_SUBJECTS_AS_SET ] = subjects_as_set
897
+
898
+ return subjects_as_set
899
+
900
+ def get_org_admins (self ):
901
+ # This retrieves the value of the org to be added if the user has one or
902
+ # more org-admin groups, otherwise the empty list.
903
+ return [
904
+ group [: - len (self .ORG_ADMIN_SUFFIX )]
905
+ for group in self .payload .get ("groups" , [])
906
+ if group .endswith (self .ORG_ADMIN_SUFFIX )
907
+ ]
908
+
909
+ def get_resource_admins (self ):
910
+ # This retrieves the value of the access-id to be added if the user has one or
911
+ # more resource-admin groups, otherwise the empty list.
912
+ return [
913
+ group [: - len (self .RESOURCE_ADMIN_SUFFIX )]
914
+ for group in self .payload .get ("groups" , [])
915
+ if group .endswith (self .RESOURCE_ADMIN_SUFFIX )
916
+ ]
917
+
918
+ def _active_groups (self ):
919
+ """
920
+ Attempts to filter groups to just the ones that are currently valid for this
921
+ user. If they have a colon, the prefix leading up to the colon must be the
922
+ user's current org, otherwise the user should not actually have rights with
923
+ this group.
924
+ """
925
+ org = self .payload .get ("org" )
926
+ for group in self .payload .get ("groups" , []):
927
+ parts = group .split (":" )
928
+
929
+ if len (parts ) == 1 :
930
+ yield group
931
+ elif org and parts [0 ] == org :
932
+ yield group
933
+
934
+ def _clear_cache (self ):
935
+ for key in (
936
+ self .KEY_PAYLOAD ,
937
+ self .KEY_ALL_ACL_SUBJECTS ,
938
+ self .KEY_ALL_ACL_SUBJECTS_AS_SET ,
939
+ self .KEY_ALL_OWNER_ACL_SUBJECTS ,
940
+ self .KEY_ALL_OWNER_ACL_SUBJECTS_AS_SET ,
941
+ ):
942
+ if key in self .__dict__ :
943
+ del self .__dict__ [key ]
944
+ self ._namespace = None
945
+
800
946
def __getstate__ (self ):
801
947
return dict ((attr , getattr (self , attr )) for attr in self .__attrs__ )
802
948
0 commit comments