Skip to content

Commit 64819e2

Browse files
committed
Use old school single inheritance to replace MixIn
1 parent ff361bd commit 64819e2

File tree

1 file changed

+131
-109
lines changed

1 file changed

+131
-109
lines changed

tests/test_e2e.py

Lines changed: 131 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -9,82 +9,16 @@
99

1010

1111
logger = logging.getLogger(__name__)
12-
logging.basicConfig(level=logging.DEBUG)
12+
logging.basicConfig(level=logging.INFO)
1313

14-
def get_lab_user(query):
15-
# Based on https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/LAB.aspx
16-
user = requests.get("https://api.msidlab.com/api/user", params=query).json()
17-
return { # Mapping lab API response to our expected configuration format
18-
"authority": user["Authority"][0] + user["Users"]["tenantId"],
19-
"client_id": user["AppID"],
20-
"username": user["Users"]["upn"],
21-
"password": "TBD", # TODO
22-
"scope": ["https://graph.microsoft.com/.default"],
23-
}
24-
25-
def get_lab_app(
26-
env_client_id="LAB_APP_CLIENT_ID",
27-
env_client_secret="LAB_APP_CLIENT_SECRET",
28-
):
29-
"""Returns the lab app as an MSAL confidential client.
3014

31-
Get it from environment variables if defined, otherwise fall back to use MSI.
32-
"""
33-
if os.getenv(env_client_id) and os.getenv(env_client_secret):
34-
# A shortcut mainly for running tests on developer's local development machine
35-
# or it could be setup on Travis CI
36-
# https://docs.travis-ci.com/user/environment-variables/#defining-variables-in-repository-settings
37-
# Data came from here
38-
# https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/Rese.aspx#programmatic-access-info-for-lab-request-api
39-
logger.info("Using lap app defined by ENV variables %s and %s",
40-
env_client_id, env_client_secret)
41-
client_id = os.getenv(env_client_id)
42-
client_secret = os.getenv(env_client_secret)
43-
else:
44-
logger.info("ENV variables %s and/or %s are not defined. Fall back to MSI.",
45-
env_client_id, env_client_secret)
46-
# See also https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/Programmatically-accessing-LAB-API's.aspx
47-
raise NotImplementedError("MSI-based mechanism has not been implemented yet")
48-
return msal.ConfidentialClientApplication(client_id, client_secret,
49-
authority="https://login.microsoftonline.com/"
50-
"72f988bf-86f1-41af-91ab-2d7cd011db47", # Microsoft tenant ID
51-
)
52-
53-
def get_lab_user_secret(access_token, lab_name="msidlab4"):
54-
return requests.get(
55-
# Note: Short link won't work "https://aka.ms/GetLabUserSecret?Secret=%s"
56-
"https://request.msidlab.com/api/GetLabUserSecret?code=KpY5uCcoKo0aW8VOL/CUO3wnu9UF2XbSnLFGk56BDnmQiwD80MQ7HA==&Secret=%s"
57-
% lab_name,
58-
headers={"Authorization": "Bearer %s" % access_token},
59-
).json()["Value"]
60-
61-
62-
@unittest.skip("for now")
6315
class E2eTestCase(unittest.TestCase):
64-
"""
65-
lab_token = get_lab_app().acquire_token_for_client(
66-
"https://request.msidlab.com/.default"
67-
) # BTW, this infrastructure tests the confidential client flow
68-
lab_password = get_lab_user_secret(lab_token["access_token"])
69-
"""
70-
71-
def setUp(self):
72-
pass
73-
# client_id, client_secret = get_lab_app()
74-
# self.lab_app = msal.ConfidentialClientApplication(client_id, client_secret)
75-
76-
def test_bar(self):
77-
self.assertEqual("********", self.lab_password)
78-
79-
80-
class BaseMixin(object):
16+
config = {}
8117

8218
def skipIfNotConfigured(self, fields):
83-
if not all(map(self.config.get, fields)):
84-
self.skipTest("Configuration not sufficient")
8519
for field in fields:
8620
if not self.config.get(field):
87-
self.skipTest('"%s" not defined in configuration' % field)
21+
self.skipTest('"%s" not found in configuration' % field)
8822

8923
def assertLoosely(self, response, assertion=None,
9024
skippable_errors=("invalid_grant", "interaction_required")):
@@ -124,8 +58,6 @@ def assertCacheWorks(self, result_from_wire):
12458
self.assertNotEqual(result['access_token'], result_from_cache['access_token'],
12559
"We should get a fresh AT (via RT)")
12660

127-
128-
class UsernamePasswordMixin(object):
12961
def test_username_password(self):
13062
self.skipIfNotConfigured([
13163
"authority", "client_id", "username", "password", "scope"])
@@ -135,59 +67,149 @@ def test_username_password(self):
13567
self.config["username"], self.config["password"],
13668
scopes=self.config.get("scope"))
13769
self.assertLoosely(result)
70+
# self.assertEqual(None, result.get("error"), str(result))
13871
self.assertCacheWorks(result)
13972

14073

141-
DEFAULT_QUERY = {"mam": False, "mfa": False}
74+
CONFIG = os.path.join(os.path.dirname(__file__), "config.json")
75+
@unittest.skipIf(not os.path.exists(CONFIG), "Optional %s not found" % CONFIG)
76+
class FileBasedTestCase(E2eTestCase):
77+
def setUp(self):
78+
with open(CONFIG) as f:
79+
self.config = json.load(f)
14280

143-
# Note: the following semi-parameterized testing approach is inspired from
144-
# https://bugs.python.org/msg151444
14581

146-
@unittest.skip("for now")
147-
class AadManagedUserPassTestCase(BaseMixin, UsernamePasswordMixin, unittest.TestCase):
148-
def setUp(self):
149-
self.config = get_lab_user(dict(DEFAULT_QUERY, isFederated=False))
82+
def get_lab_user(query): # This API requires no authorization
83+
# Based on https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/LAB.aspx
84+
user = requests.get("https://api.msidlab.com/api/user", params=query).json()
85+
return { # Mapping lab API response to our simplified configuration format
86+
"authority": user["Authority"][0] + user["Users"]["tenantId"],
87+
"client_id": user["AppID"],
88+
"username": user["Users"]["upn"],
89+
"lab": {"labname": user["Users"]["upn"].split('@')[1].split('.')[0]}, # :(
90+
"scope": ["https://graph.microsoft.com/.default"],
91+
}
15092

151-
@unittest.skip("for now")
152-
class Adfs4FedUserPassTestCase(BaseMixin, UsernamePasswordMixin, unittest.TestCase):
153-
def setUp(self):
154-
self.config = get_lab_user(dict(
93+
def get_lab_app(
94+
env_client_id="LAB_APP_CLIENT_ID",
95+
env_client_secret="LAB_APP_CLIENT_SECRET",
96+
):
97+
"""Returns the lab app as an MSAL confidential client.
98+
99+
Get it from environment variables if defined, otherwise fall back to use MSI.
100+
"""
101+
if os.getenv(env_client_id) and os.getenv(env_client_secret):
102+
# A shortcut mainly for running tests on developer's local development machine
103+
# or it could be setup on Travis CI
104+
# https://docs.travis-ci.com/user/environment-variables/#defining-variables-in-repository-settings
105+
# Data came from here
106+
# https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/Rese.aspx#programmatic-access-info-for-lab-request-api
107+
logger.info("Using lab app defined by ENV variables %s and %s",
108+
env_client_id, env_client_secret)
109+
client_id = os.getenv(env_client_id)
110+
client_secret = os.getenv(env_client_secret)
111+
else:
112+
logger.info("ENV variables %s and/or %s are not defined. Fall back to MSI.",
113+
env_client_id, env_client_secret)
114+
# See also https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/Programmatically-accessing-LAB-API's.aspx
115+
raise NotImplementedError("MSI-based mechanism has not been implemented yet")
116+
return msal.ConfidentialClientApplication(client_id, client_secret,
117+
authority="https://login.microsoftonline.com/"
118+
"72f988bf-86f1-41af-91ab-2d7cd011db47", # Microsoft tenant ID
119+
)
120+
121+
def get_session(lab_app): # BTW, this infrastructure tests the confidential client flow
122+
logger.info("Creating session")
123+
lab_token = lab_app.acquire_token_for_client("https://request.msidlab.com/.default")
124+
session = requests.Session()
125+
session.headers.update({"Authorization": "Bearer %s" % lab_token["access_token"]})
126+
session.hooks["response"].append(lambda r, *args, **kwargs: r.raise_for_status())
127+
return session
128+
129+
130+
class LabBasedTestCase(E2eTestCase):
131+
session = get_session(get_lab_app()) # It will run even all test cases are skipped
132+
_secrets = {}
133+
134+
@classmethod
135+
def get_lab_user_secret(cls, lab_name="msidlab4"):
136+
lab_name = lab_name.lower()
137+
if lab_name not in cls._secrets:
138+
logger.info("Querying lab user password for %s", lab_name)
139+
# Note: Short link won't work "https://aka.ms/GetLabUserSecret?Secret=%s"
140+
# So we use the official link written in here
141+
# https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/Programmatically-accessing-LAB-API%27s.aspx
142+
url = ("https://request.msidlab.com/api/GetLabUserSecret?code=KpY5uCcoKo0aW8VOL/CUO3wnu9UF2XbSnLFGk56BDnmQiwD80MQ7HA==&Secret=%s"
143+
% lab_name)
144+
resp = cls.session.get(url)
145+
cls._secrets[lab_name] = resp.json()["Value"]
146+
return cls._secrets[lab_name]
147+
148+
@classmethod
149+
def get_lab_user(cls, query): # The query format is in lab team's Aug 9 email
150+
resp = cls.session.get("https://user.msidlab.com/api/user", params=query)
151+
result = resp.json()[0]
152+
return { # Mapping lab API response to our simplified configuration format
153+
"authority": result["lab"]["authority"] + result["lab"]["tenantid"],
154+
"client_id": result["app"]["objectid"],
155+
"username": result["user"]["upn"],
156+
"lab": result["lab"],
157+
"scope": ["https://graph.microsoft.com/.default"],
158+
}
159+
160+
DEFAULT_QUERY = {"mam": False, "mfa": False}
161+
162+
class AadManagedUserTestCase(LabBasedTestCase):
163+
@classmethod
164+
def setUpClass(cls):
165+
cls.config = get_lab_user(dict(DEFAULT_QUERY,
166+
isFederated=False, # Supposed to find a pure managed user,
167+
# but lab still gives us a [email protected]
168+
))
169+
cls.config["password"] = cls.get_lab_user_secret(cls.config["lab"]["labname"])
170+
171+
class Adfs4FedUserTestCase(LabBasedTestCase):
172+
@classmethod
173+
def setUpClass(cls):
174+
cls.config = get_lab_user(dict(
155175
DEFAULT_QUERY, isFederated=True, federationProvider="ADFSv4"))
176+
cls.config["password"] = cls.get_lab_user_secret(cls.config["lab"]["labname"])
156177

157-
@unittest.skip("for now")
158-
class Adfs4ManagedUserPassTestCase(BaseMixin, UsernamePasswordMixin, unittest.TestCase):
159-
def setUp(self):
160-
self.config = get_lab_user(dict(
178+
class Adfs4ManagedUserTestCase(LabBasedTestCase): # a.k.a. the hybrid
179+
@classmethod
180+
def setUpClass(cls):
181+
cls.config = get_lab_user(dict(
161182
DEFAULT_QUERY, isFederated=False, federationProvider="ADFSv4"))
183+
cls.config["password"] = cls.get_lab_user_secret(cls.config["lab"]["labname"])
162184

163-
@unittest.skip("for now") # TODO: Need to pick up the real password
164-
class Adfs3FedUserPassTestCase(BaseMixin, UsernamePasswordMixin, unittest.TestCase):
165-
def setUp(self):
166-
self.config = get_lab_user(dict(
185+
class Adfs3FedUserTestCase(LabBasedTestCase):
186+
@classmethod
187+
def setUpClass(cls):
188+
cls.config = get_lab_user(dict(
167189
DEFAULT_QUERY, isFederated=True, federationProvider="ADFSv3"))
168-
169-
@unittest.skip("for now") # TODO: Need to pick up the real password
170-
class Adfs3ManagedUserPassTestCase(BaseMixin, UsernamePasswordMixin, unittest.TestCase):
171-
def setUp(self):
172-
self.config = get_lab_user(dict(
190+
#cls.config = cls.get_lab_user({
191+
# "MFA": "none", "UserType": "federated", "FederationProvider": "adfsv3"})
192+
cls.config["password"] = cls.get_lab_user_secret(cls.config["lab"]["labname"])
193+
194+
class Adfs3ManagedUserTestCase(LabBasedTestCase): # a.k.a. the hybrid
195+
@classmethod
196+
def setUpClass(cls):
197+
cls.config = get_lab_user(dict(
173198
DEFAULT_QUERY, isFederated=False, federationProvider="ADFSv3"))
199+
cls.config["password"] = cls.get_lab_user_secret(cls.config["lab"]["labname"])
174200

175-
@unittest.skip("for now") # TODO: Need to pick up the real password
176-
class Adfs2FedUserPassTestCase(BaseMixin, UsernamePasswordMixin, unittest.TestCase):
177-
def setUp(self):
178-
self.config = get_lab_user(dict(
201+
class Adfs2FedUserTestCase(LabBasedTestCase):
202+
@classmethod
203+
def setUpClass(cls):
204+
cls.config = get_lab_user(dict(
179205
DEFAULT_QUERY, isFederated=True, federationProvider="ADFSv2"))
206+
cls.config["password"] = cls.get_lab_user_secret(cls.config["lab"]["labname"])
180207

181208
@unittest.skip("Lab API returns nothing. We might need to switch to beta api")
182-
class Adfs2019FedUserPassTestCase(BaseMixin, UsernamePasswordMixin, unittest.TestCase):
183-
def setUp(self):
184-
self.config = get_lab_user(dict(
209+
class Adfs2019FedUserTestCase(LabBasedTestCase):
210+
@classmethod
211+
def setUpClass(cls):
212+
cls.config = get_lab_user(dict(
185213
DEFAULT_QUERY, isFederated=True, federationProvider="ADFSv2019"))
186-
187-
CONFIG = os.path.join(os.path.dirname(__file__), "config.json")
188-
@unittest.skipIf(not os.path.exists(CONFIG), "Optional %s not found" % CONFIG)
189-
class FileBasedTestCase(BaseMixin, UsernamePasswordMixin, unittest.TestCase):
190-
def setUp(self):
191-
with open(CONFIG) as f:
192-
self.config = json.load(f)
214+
cls.config["password"] = cls.get_lab_user_secret(cls.config["lab"]["labname"])
193215

0 commit comments

Comments
 (0)