Skip to content

Commit f56b2c7

Browse files
authored
feat: add JWTAuth (#1219)
* feat: add JWTAuth, add repr using qualname * chore: mark Credentials class and methods as abstract
1 parent 66064c5 commit f56b2c7

File tree

2 files changed

+49
-9
lines changed

2 files changed

+49
-9
lines changed

tableauserverclient/models/tableau_auth.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
class Credentials:
1+
import abc
2+
3+
4+
class Credentials(abc.ABC):
25
def __init__(self, site_id=None, user_id_to_impersonate=None):
36
self.site_id = site_id or ""
47
self.user_id_to_impersonate = user_id_to_impersonate or None
58

69
@property
10+
@abc.abstractmethod
711
def credentials(self):
812
credentials = "Credentials can be username/password, Personal Access Token, or JWT"
913
+"This method returns values to set as an attribute on the credentials element of the request"
1014

15+
@abc.abstractmethod
1116
def __repr__(self):
1217
return "All Credentials types must have a debug display that does not print secrets"
1318

@@ -52,10 +57,10 @@ def site(self, value):
5257

5358

5459
class PersonalAccessTokenAuth(Credentials):
55-
def __init__(self, token_name, personal_access_token, site_id=None):
60+
def __init__(self, token_name, personal_access_token, site_id=None, user_id_to_impersonate=None):
5661
if personal_access_token is None or token_name is None:
5762
raise TabError("Must provide a token and token name when using PAT authentication")
58-
super().__init__(site_id=site_id)
63+
super().__init__(site_id=site_id, user_id_to_impersonate=user_id_to_impersonate)
5964
self.token_name = token_name
6065
self.personal_access_token = personal_access_token
6166

@@ -70,3 +75,22 @@ def __repr__(self):
7075
return "<PersonalAccessToken name={} token={}>(site={})".format(
7176
self.token_name, self.personal_access_token[:2] + "...", self.site_id
7277
)
78+
79+
80+
class JWTAuth(Credentials):
81+
def __init__(self, jwt=None, site_id=None, user_id_to_impersonate=None):
82+
if jwt is None:
83+
raise TabError("Must provide a JWT token when using JWT authentication")
84+
super().__init__(site_id, user_id_to_impersonate)
85+
self.jwt = jwt
86+
87+
@property
88+
def credentials(self):
89+
return {"jwt": self.jwt}
90+
91+
def __repr__(self):
92+
if self.user_id_to_impersonate:
93+
uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
94+
else:
95+
uid = ""
96+
return f"<{self.__class__.__qualname__}(jwt={self.jwt[:5]}..., site_id={self.site_id}{uid})>"

tableauserverclient/server/endpoint/auth_endpoint.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import logging
2+
from typing import TYPE_CHECKING
3+
import warnings
24

35
from defusedxml.ElementTree import fromstring
46

@@ -8,6 +10,10 @@
810

911
from tableauserverclient.helpers.logging import logger
1012

13+
if TYPE_CHECKING:
14+
from tableauserverclient.models.site_item import SiteItem
15+
from tableauserverclient.models.tableau_auth import Credentials
16+
1117

1218
class Auth(Endpoint):
1319
class contextmgr(object):
@@ -21,11 +27,21 @@ def __exit__(self, exc_type, exc_val, exc_tb):
2127
self._callback()
2228

2329
@property
24-
def baseurl(self):
30+
def baseurl(self) -> str:
2531
return "{0}/auth".format(self.parent_srv.baseurl)
2632

2733
@api(version="2.0")
28-
def sign_in(self, auth_req):
34+
def sign_in(self, auth_req: "Credentials") -> contextmgr:
35+
"""
36+
Sign in to a Tableau Server or Tableau Online using a credentials object.
37+
38+
The credentials object can either be a TableauAuth object, a
39+
PersonalAccessTokenAuth object, or a JWTAuth object. This method now
40+
accepts them all. The object should be populated with the site_id and
41+
optionally a user_id to impersonate.
42+
43+
Creates a context manager that will sign out of the server upon exit.
44+
"""
2945
url = "{0}/{1}".format(self.baseurl, "signin")
3046
signin_req = RequestFactory.Auth.signin_req(auth_req)
3147
server_response = self.parent_srv.session.post(
@@ -51,12 +67,12 @@ def sign_in(self, auth_req):
5167
return Auth.contextmgr(self.sign_out)
5268

5369
@api(version="3.6")
54-
def sign_in_with_personal_access_token(self, auth_req):
70+
def sign_in_with_personal_access_token(self, auth_req: "Credentials") -> contextmgr:
5571
# We use the same request that username/password login uses.
5672
return self.sign_in(auth_req)
5773

5874
@api(version="2.0")
59-
def sign_out(self):
75+
def sign_out(self) -> None:
6076
url = "{0}/{1}".format(self.baseurl, "signout")
6177
# If there are no auth tokens you're already signed out. No-op
6278
if not self.parent_srv.is_signed_in():
@@ -66,7 +82,7 @@ def sign_out(self):
6682
logger.info("Signed out")
6783

6884
@api(version="2.6")
69-
def switch_site(self, site_item):
85+
def switch_site(self, site_item: "SiteItem") -> contextmgr:
7086
url = "{0}/{1}".format(self.baseurl, "switchSite")
7187
switch_req = RequestFactory.Auth.switch_req(site_item.content_url)
7288
try:
@@ -87,7 +103,7 @@ def switch_site(self, site_item):
87103
return Auth.contextmgr(self.sign_out)
88104

89105
@api(version="3.10")
90-
def revoke_all_server_admin_tokens(self):
106+
def revoke_all_server_admin_tokens(self) -> None:
91107
url = "{0}/{1}".format(self.baseurl, "revokeAllServerAdminTokens")
92108
self.post_request(url, "")
93109
logger.info("Revoked all tokens for all server admins")

0 commit comments

Comments
 (0)