Skip to content

Commit 230ad42

Browse files
authored
Merge pull request #12 from AASHE/add-security-services
Add security module to membersuite_api_client
2 parents 77dbb59 + cb1f14e commit 230ad42

9 files changed

Lines changed: 166 additions & 49 deletions

File tree

membersuite_api_client/client.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import base64
55
import hmac
66

7+
from .utils import get_session_id
8+
9+
710
XHTML_NAMESPACE = "http://membersuite.com/schemas"
811

912

@@ -39,12 +42,6 @@ def get_hashed_signature(self, url):
3942
hashed = hmac.new(secret_b, data_b, sha1).digest()
4043
return base64.b64encode(hashed).decode("utf-8")
4144

42-
def get_session_id_from_login_result(self, login_result):
43-
try:
44-
return login_result["header"]["header"]["SessionId"]
45-
except TypeError:
46-
return None
47-
4845
def request_session(self):
4946
"""
5047
Performs initial request to initialize session and get session id
@@ -57,12 +54,11 @@ def request_session(self):
5754
result = self.client.service.WhoAmI(
5855
_soapheaders=[concierge_request_header])
5956

60-
self.session_id = self.get_session_id_from_login_result(
61-
login_result=result)
57+
self.session_id = get_session_id(result=result)
6258

6359
if not self.session_id:
6460
raise MembersuiteLoginError(
65-
result["body"]["LoginResult"]["Errors"])
61+
result["body"]["WhoAmIResult"]["Errors"])
6662

6763
return self.session_id
6864

@@ -135,16 +131,6 @@ def query_orgs(self, parameters=None, since_when=None):
135131
else:
136132
return None
137133

138-
def convert_ms_object(self, ms_object):
139-
"""
140-
Converts the list of dictionaries with keys "key" and "value" into
141-
more logical value-key pairs in a plain dictionary.
142-
"""
143-
out_dict = {}
144-
for item in ms_object:
145-
out_dict[item["Key"]] = item["Value"]
146-
return out_dict
147-
148134
def runSQL(self, query, start_record=0):
149135
concierge_request_header = self.construct_concierge_header(
150136
url="http://membersuite.com/contracts/"

membersuite_api_client/security/__init__.py

Whitespace-only changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from ..utils import convert_ms_object
2+
3+
4+
class PortalUser(object):
5+
6+
def __init__(self, portal_user, session_id=None):
7+
"""Create a PortalUser object from a the Zeep'ed XML representation of
8+
a Membersuite PortalUser object.
9+
10+
"""
11+
fields = convert_ms_object(portal_user["Fields"]
12+
["KeyValueOfstringanyType"])
13+
14+
self.id = fields["ID"]
15+
self.email_address = fields["EmailAddress"]
16+
self.first_name = fields["FirstName"]
17+
self.last_name = fields["LastName"]
18+
19+
self.session_id = session_id
20+
21+
self.extra_data = portal_user
22+
23+
def get_username(self):
24+
return "_membersuite_id_{}".format(self.id)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from .models import PortalUser
2+
from ..utils import get_session_id
3+
4+
5+
class LoginToPortalError(Exception):
6+
7+
def __init__(self, result):
8+
self.result = result
9+
10+
11+
def login_to_portal(username, password, client):
12+
"""Log `username` into the MemberSuite Portal.
13+
14+
Returns a PortalUser object if successful, raises
15+
LoginToPortalError if not.
16+
17+
"""
18+
if not client.session_id:
19+
client.request_session()
20+
21+
concierge_request_header = client.construct_concierge_header(
22+
url=("http://membersuite.com/contracts/IConciergeAPIService/"
23+
"LoginToPortal"))
24+
25+
result = client.client.service.LoginToPortal(
26+
_soapheaders=[concierge_request_header],
27+
portalUserName=username,
28+
portalPassword=password)
29+
30+
login_to_portal_result = result["body"]["LoginToPortalResult"]
31+
32+
if login_to_portal_result["Success"]:
33+
portal_user = login_to_portal_result["ResultValue"]["PortalUser"]
34+
35+
session_id = get_session_id(result=result)
36+
37+
return PortalUser(portal_user=portal_user,
38+
session_id=session_id)
39+
else:
40+
raise LoginToPortalError(result=result)

membersuite_api_client/subscriptions/services.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"""
1818

1919
from .models import Subscription
20+
from ..utils import convert_ms_object
2021

2122

2223
class SubscriptionService(object):
@@ -48,7 +49,7 @@ def get_org_subscriptions(self, org_id, publication_id=None):
4849

4950
subscription_list = []
5051
for obj in objects:
51-
sane_obj = self.client.convert_ms_object(
52+
sane_obj = convert_ms_object(
5253
obj['Fields']['KeyValueOfstringanyType'])
5354
subscription = Subscription(
5455
id=sane_obj['ID'],

membersuite_api_client/tests/test_client.py

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
import datetime
12
import os
23
import unittest
34

45
from ..client import ConciergeClient
5-
import datetime
6+
from ..utils import get_session_id
67

78

8-
MS_USER_ID = os.environ.get("MS_USER_ID", None)
9-
MS_USER_PASS = os.environ.get("MS_USER_PASS", None)
109
MS_ACCESS_KEY = os.environ["MS_ACCESS_KEY"]
1110
MS_SECRET_KEY = os.environ["MS_SECRET_KEY"]
1211
MS_ASSOCIATION_ID = os.environ["MS_ASSOCIATION_ID"]
@@ -55,9 +54,8 @@ def test_request_session(self):
5554
# Check that the session ID in the response headers matches the
5655
# previously obtained session, so the user was not re-authenticated
5756
# but properly used the established session.
58-
self.assertEqual(
59-
client.get_session_id_from_login_result(login_result=response),
60-
client.session_id)
57+
self.assertEqual(get_session_id(result=response),
58+
client.session_id)
6159

6260
def test_query_orgs(self):
6361
"""
@@ -84,29 +82,6 @@ def test_query_orgs(self):
8482
response = client.query_orgs(parameters, since_when)
8583
# self.assertFalse(response)
8684

87-
def test_convert_ms_object(self):
88-
"""
89-
Can we parse the list of dicts for org attributes into a dict?
90-
"""
91-
client = ConciergeClient(access_key=MS_ACCESS_KEY,
92-
secret_key=MS_SECRET_KEY,
93-
association_id=MS_ASSOCIATION_ID)
94-
95-
# Send a login request to receive a session id
96-
session_id = client.request_session()
97-
self.assertTrue(session_id)
98-
parameters = {
99-
'Name': 'AASHE Test Campus',
100-
}
101-
response = client.query_orgs(parameters)
102-
self.assertEqual(response[0]["Fields"]["KeyValueOfstringanyType"]
103-
[28]["Value"],
104-
'AASHE Test Campus')
105-
converted_dict = client.convert_ms_object(
106-
response[0]["Fields"]["KeyValueOfstringanyType"])
107-
108-
self.assertEqual(converted_dict["Name"], "AASHE Test Campus")
109-
11085

11186
if __name__ == '__main__':
11287
unittest.main()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import os
2+
import unittest
3+
4+
from ..client import ConciergeClient
5+
from ..security.models import PortalUser
6+
from ..security.services import login_to_portal, LoginToPortalError
7+
8+
9+
class SecurityServicesTestCase(unittest.TestCase):
10+
11+
@classmethod
12+
def setUpClass(cls):
13+
cls.client = ConciergeClient(
14+
access_key=os.environ["MS_ACCESS_KEY"],
15+
secret_key=os.environ["MS_SECRET_KEY"],
16+
association_id=os.environ["MS_ASSOCIATION_ID"])
17+
18+
def test_login_to_portal(self):
19+
"""Can we log in to the portal?"""
20+
portal_user = login_to_portal(
21+
username=os.environ["TEST_MS_PORTAL_USER_ID"],
22+
password=os.environ["TEST_MS_PORTAL_USER_PASS"],
23+
client=self.client)
24+
self.assertIsInstance(portal_user, PortalUser)
25+
26+
def test_login_to_portal_failure(self):
27+
"""What happens when we can't log in to the portal?"""
28+
with self.assertRaises(LoginToPortalError):
29+
login_to_portal(username="bo-o-o-gus user ID",
30+
password="wrong password",
31+
client=self.client)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import os
2+
import unittest
3+
4+
from ..client import ConciergeClient
5+
from ..utils import convert_ms_object
6+
7+
8+
MS_ACCESS_KEY = os.environ["MS_ACCESS_KEY"]
9+
MS_SECRET_KEY = os.environ["MS_SECRET_KEY"]
10+
MS_ASSOCIATION_ID = os.environ["MS_ASSOCIATION_ID"]
11+
12+
13+
class UtilsTestCase(unittest.TestCase):
14+
15+
def test_convert_ms_object(self):
16+
"""
17+
Can we parse the list of dicts for org attributes into a dict?
18+
"""
19+
client = ConciergeClient(access_key=MS_ACCESS_KEY,
20+
secret_key=MS_SECRET_KEY,
21+
association_id=MS_ASSOCIATION_ID)
22+
23+
# Send a login request to receive a session id
24+
session_id = client.request_session()
25+
self.assertTrue(session_id)
26+
parameters = {
27+
'Name': 'AASHE Test Campus',
28+
}
29+
response = client.query_orgs(parameters)
30+
self.assertEqual(response[0]["Fields"]["KeyValueOfstringanyType"]
31+
[28]["Value"],
32+
'AASHE Test Campus')
33+
converted_dict = convert_ms_object(
34+
response[0]["Fields"]["KeyValueOfstringanyType"])
35+
36+
self.assertEqual(converted_dict["Name"], "AASHE Test Campus")
37+
38+
39+
if __name__ == '__main__':
40+
unittest.main()

membersuite_api_client/utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
def convert_ms_object(ms_object):
2+
"""
3+
Converts the list of dictionaries with keys "key" and "value" into
4+
more logical value-key pairs in a plain dictionary.
5+
"""
6+
out_dict = {}
7+
for item in ms_object:
8+
out_dict[item["Key"]] = item["Value"]
9+
return out_dict
10+
11+
12+
def get_session_id(result):
13+
"""Returns the Session ID for an API result.
14+
15+
When there's no Session ID, returns None.
16+
"""
17+
try:
18+
return result["header"]["header"]["SessionId"]
19+
except TypeError:
20+
return None

0 commit comments

Comments
 (0)