Skip to content

Commit 51e6168

Browse files
authored
Merge pull request #8 from onelogin/mfa_methods
Add support for Multi-Factor Authentication API
2 parents 13b6251 + 40dc8fe commit 51e6168

File tree

7 files changed

+314
-22
lines changed

7 files changed

+314
-22
lines changed

README.md

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -186,24 +186,6 @@ created_user = client.create_user(new_user_params)
186186
# Delete User
187187
result = client.delete_user(created_user.id)
188188

189-
# Create Session Login Token
190-
session_login_token_params = {
191-
"username_or_email": "user@example.com",
192-
"password": "Aa765431-XxX",
193-
"subdomain": "example-onelogin-subdomain"
194-
}
195-
session_token_data = client.create_session_login_token(session_login_token_params);
196-
197-
# Create Session Login Token MFA , after verify
198-
session_login_token_mfa_params = {
199-
"username_or_email": "usermfa@example.com",
200-
"password": "Aa765432-YyY",
201-
"subdomain": "example-onelogin-subdomain"
202-
}
203-
session_token_mfa_data = client.create_session_login_token(session_login_token_mfa_params)
204-
otp_token = "000000"; // We may take that value from OTP device
205-
session_token_data2 = client.get_session_token_verified(session_token_mfa_data.devices[0].id, session_token_mfa_data.state_token, otp_token);
206-
207189
# Get EventTypes
208190
event_types = client.get_event_types()
209191

@@ -246,6 +228,44 @@ mfa = saml_endpoint_response2.mfa
246228
otp_token = "000000";
247229
saml_endpoint_response_after_verify = client.get_saml_assertion_verifying(app_id, mfa.devices[0].id, mfa.state_token, "78395727", None);
248230

231+
# Create Session Login Token
232+
session_login_token_params = {
233+
"username_or_email": "user@example.com",
234+
"password": "Aa765431-XxX",
235+
"subdomain": "example-onelogin-subdomain"
236+
}
237+
session_token_data = client.create_session_login_token(session_login_token_params);
238+
239+
# Create Session Via API Token
240+
cookie = client.create_session_via_token(session_token_data.session_token)
241+
242+
# Create Session Login Token MFA , after verify
243+
session_login_token_mfa_params = {
244+
"username_or_email": "usermfa@example.com",
245+
"password": "Aa765432-YyY",
246+
"subdomain": "example-onelogin-subdomain"
247+
}
248+
session_token_mfa_data = client.create_session_login_token(session_login_token_mfa_params)
249+
otp_token = "000000"; // We may take that value from OTP device
250+
session_token_data2 = client.get_session_token_verified(session_token_mfa_data.devices[0].id, session_token_mfa_data.state_token, otp_token);
251+
252+
user_id = 00000000
253+
# Get Available Authentication Factors
254+
auth_factors = client.get_factors(user_id)
255+
256+
# Enroll an Authentication Factor
257+
enroll_factor = client.enroll_factor(user_id, auth_factors[0].id, 'My Device', '+14156456830')
258+
259+
# Get Enrolled Authentication Factors
260+
otp_devices = client.get_enrolled_factors(user_id)
261+
262+
# Activate an Authentication Factor
263+
device_id = 0000000
264+
enrollment_response = client.activate_factor(user_id, device_id)
265+
266+
# Verify an Authentication Factor
267+
result = client.verify_factor(user_id, device_id, otp_token="4242342423")
268+
249269
# Generate Invite Link
250270
url_link = client.generate_invite_link("user@example.com")
251271

src/onelogin/api/client.py

Lines changed: 229 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
from onelogin.api.util.urlbuilder import UrlBuilder
1818
from onelogin.api.util.constants import Constants
1919
from onelogin.api.models.app import App
20+
from onelogin.api.models.auth_factor import AuthFactor
2021
from onelogin.api.models.event import Event
2122
from onelogin.api.models.embed_app import EmbedApp
2223
from onelogin.api.models.event_type import EventType
24+
from onelogin.api.models.factor_enrollment_response import FactorEnrollmentResponse
2325
from onelogin.api.models.group import Group
2426
from onelogin.api.models.mfa import MFA
2527
from onelogin.api.models.onelogin_token import OneLoginToken
28+
from onelogin.api.models.otp_device import OTP_Device
2629
from onelogin.api.models.rate_limit import RateLimit
2730
from onelogin.api.models.role import Role
2831
from onelogin.api.models.saml_endpoint_response import SAMLEndpointResponse
@@ -70,8 +73,8 @@ def clean_error(self):
7073
self.error = None
7174
self.error_description = None
7275

73-
def get_url(self, base, obj_id=None):
74-
return self.url_builder.get_url(base, obj_id)
76+
def get_url(self, base, obj_id=None, extra_id=None):
77+
return self.url_builder.get_url(base, obj_id, extra_id)
7578

7679
def extract_error_message_from_response(self, response):
7780
message = ''
@@ -1031,6 +1034,8 @@ def create_session_via_token(self, session_token):
10311034
:return: return the 'Set-Cookie' value of the HTTP Header if any
10321035
:rtype: string
10331036
1037+
See @see https://developers.onelogin.com/api-docs/1/login-page/create-session-via-token Create Session Via API Token documentation
1038+
10341039
"""
10351040
url = self.get_url(Constants.SESSION_API_TOKEN_URL)
10361041
headers = {
@@ -1507,6 +1512,228 @@ def get_saml_assertion_verifying(self, app_id, device_id, state_token, otp_token
15071512
self.error = 500
15081513
self.error_description = e.args[0]
15091514

1515+
# Multi-factor Auth Methods
1516+
def get_factors(self, user_id):
1517+
"""
1518+
1519+
Returns a list of authentication factors that are available for user enrollment via API.
1520+
1521+
:param user_id: Set to the id of the user.
1522+
:type user_id: integer
1523+
1524+
:return: AuthFactor list
1525+
:rtype: array
1526+
1527+
See @see https://developers.onelogin.com/api-docs/1/multi-factor-authentication/available-factors Get Available Authentication Factors documentation
1528+
1529+
"""
1530+
self.clean_error()
1531+
self.prepare_token()
1532+
1533+
try:
1534+
url = self.get_url(Constants.GET_FACTORS_URL, user_id)
1535+
headers = self.get_authorized_headers()
1536+
1537+
response = requests.get(url, headers=headers)
1538+
1539+
auth_factors = []
1540+
if response.status_code == 200:
1541+
json_data = response.json()
1542+
if json_data and json_data.get('data', None):
1543+
for auth_factor_data in json_data['data']['auth_factors']:
1544+
auth_factors.append(AuthFactor(auth_factor_data))
1545+
else:
1546+
self.error = str(response.status_code)
1547+
self.error_description = self.extract_error_message_from_response(response)
1548+
1549+
return auth_factors
1550+
except Exception as e:
1551+
self.error = 500
1552+
self.error_description = e.args[0]
1553+
1554+
def enroll_factor(self, user_id, factor_id, display_name, number):
1555+
"""
1556+
1557+
Enroll a user with a given authentication factor.
1558+
1559+
:param user_id: Set to the id of the user.
1560+
:type user_id: integer
1561+
1562+
:param factor_id: The identifier of the factor to enroll the user with.
1563+
:type factor_id: integer
1564+
1565+
:param display_name: A name for the users device.
1566+
:type display_name: string
1567+
1568+
:param number: The phone number of the user in E.164 format..
1569+
:type number: string
1570+
1571+
:return: MFA device
1572+
:rtype: OTP_Device
1573+
1574+
See @see https://developers.onelogin.com/api-docs/1/multi-factor-authentication/enroll-factor Enroll an Authentication Factor documentation
1575+
1576+
"""
1577+
self.clean_error()
1578+
self.prepare_token()
1579+
1580+
try:
1581+
url = self.get_url(Constants.ENROLL_FACTOR_URL, user_id)
1582+
headers = self.get_authorized_headers()
1583+
1584+
data = {
1585+
'factor_id': int(factor_id),
1586+
'display_name': display_name,
1587+
'number': number
1588+
}
1589+
1590+
response = requests.post(url, headers=headers, json=data)
1591+
1592+
if response.status_code == 200:
1593+
json_data = response.json()
1594+
if json_data and json_data.get('data', None):
1595+
return OTP_Device(json_data['data'][0])
1596+
else:
1597+
self.error = str(response.status_code)
1598+
self.error_description = self.extract_error_message_from_response(response)
1599+
except Exception as e:
1600+
self.error = 500
1601+
self.error_description = e.args[0]
1602+
1603+
def get_enrolled_factors(self, user_id):
1604+
"""
1605+
1606+
Enroll a user with a given authentication factor.
1607+
1608+
:param user_id: Set to the id of the user.
1609+
:type user_id: integer
1610+
1611+
:return: OTP_Device list
1612+
:rtype: array
1613+
1614+
See @see https://developers.onelogin.com/api-docs/1/multi-factor-authentication/enrolled-factors Get Enrolled Authentication Factors documentation
1615+
1616+
"""
1617+
self.clean_error()
1618+
self.prepare_token()
1619+
1620+
try:
1621+
url = self.get_url(Constants.GET_ENROLLED_FACTORS_URL, user_id)
1622+
headers = self.get_authorized_headers()
1623+
1624+
response = requests.get(url, headers=headers)
1625+
1626+
otp_devices = []
1627+
if response.status_code == 200:
1628+
json_data = response.json()
1629+
if json_data and json_data.get('data', None):
1630+
otp_devices_data = json_data['data'].get('otp_devices', None)
1631+
if otp_devices_data:
1632+
for otp_device_data in otp_devices_data:
1633+
otp_devices.append(OTP_Device(otp_device_data))
1634+
else:
1635+
self.error = str(response.status_code)
1636+
self.error_description = self.extract_error_message_from_response(response)
1637+
1638+
return otp_devices
1639+
except Exception as e:
1640+
self.error = 500
1641+
self.error_description = e.args[0]
1642+
1643+
def activate_factor(self, user_id, device_id):
1644+
"""
1645+
1646+
Triggers an SMS or Push notification containing a One-Time Password (OTP)
1647+
that can be used to authenticate a user with the Verify Factor call.
1648+
1649+
:param user_id: Set to the id of the user.
1650+
:type user_id: integer
1651+
1652+
:param device_id: Set to the device_id of the MFA device.
1653+
:type device_id: integer
1654+
1655+
:return: Info with User Id, Device Id, and otp_device
1656+
:rtype: FactorEnrollmentResponse
1657+
1658+
See @see https://developers.onelogin.com/api-docs/1/multi-factor-authentication/activate-factor Activate an Authentication Factor documentation
1659+
1660+
"""
1661+
self.clean_error()
1662+
self.prepare_token()
1663+
1664+
try:
1665+
url = self.get_url(Constants.ACTIVATE_FACTOR_URL, user_id, device_id)
1666+
headers = self.get_authorized_headers()
1667+
1668+
response = requests.post(url, headers=headers)
1669+
1670+
if response.status_code == 200:
1671+
json_data = response.json()
1672+
if json_data and json_data.get('data', None):
1673+
return FactorEnrollmentResponse(json_data['data'][0])
1674+
else:
1675+
self.error = str(response.status_code)
1676+
self.error_description = self.extract_error_message_from_response(response)
1677+
except Exception as e:
1678+
self.error = 500
1679+
self.error_description = e.args[0]
1680+
1681+
def verify_factor(self, user_id, device_id, otp_token=None, state_token=None):
1682+
"""
1683+
1684+
Triggers an SMS or Push notification containing a One-Time Password (OTP)
1685+
that can be used to authenticate a user with the Verify Factor call.
1686+
1687+
:param user_id: Set to the id of the user.
1688+
:type user_id: integer
1689+
1690+
:param device_id: Set to the device_id of the MFA device.
1691+
:type device_id: integer
1692+
1693+
:param otp_token: OTP code provided by the device or SMS message sent to user.
1694+
When a device like OneLogin Protect that supports Push has
1695+
been used you do not need to provide the otp_token.
1696+
:type otp_token: string
1697+
1698+
:param state_token: The state_token is returned after a successful request
1699+
to Enroll a Factor or Activate a Factor.
1700+
MUST be provided if the needs_trigger attribute from
1701+
the proceeding calls is set to true.
1702+
:type state_token: string
1703+
1704+
:return: Info with User Id, Device Id, and otp_device
1705+
:rtype: FactorEnrollmentResponse
1706+
1707+
See @see https://developers.onelogin.com/api-docs/1/multi-factor-authentication/activate-factor Activate an Authentication Factor documentation
1708+
1709+
"""
1710+
self.clean_error()
1711+
self.prepare_token()
1712+
1713+
try:
1714+
url = self.get_url(Constants.VERIFY_FACTOR_URL, user_id, device_id)
1715+
headers = self.get_authorized_headers()
1716+
1717+
data = {}
1718+
if otp_token:
1719+
data['otp_token'] = otp_token
1720+
if state_token:
1721+
data['state_token'] = state_token
1722+
1723+
if data:
1724+
response = requests.post(url, headers=headers, json=data)
1725+
else:
1726+
response = requests.post(url, headers=headers)
1727+
1728+
if response.status_code == 200:
1729+
return self.handle_operation_response(response)
1730+
else:
1731+
self.error = str(response.status_code)
1732+
self.error_description = self.extract_error_message_from_response(response)
1733+
except Exception as e:
1734+
self.error = 500
1735+
self.error_description = e.args[0]
1736+
15101737
# Invite Links Methods
15111738
def generate_invite_link(self, email):
15121739
"""
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/python
2+
3+
4+
class AuthFactor(object):
5+
def __init__(self, data):
6+
self.name = data.get('name', '')
7+
self.id = data.get('factor_id', None)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/python
2+
3+
4+
class FactorEnrollmentResponse(object):
5+
def __init__(self, data):
6+
otp_device_id = data.get('device_id', None)
7+
self.device_id = int(otp_device_id) if otp_device_id is not None else None
8+
user_id = data.get('id', None)
9+
self.user_id = int(user_id) if user_id is not None else None
10+
self.active = data.get('active', False)
11+
self.auth_factor_name = data.get('auth_factor_name', '')
12+
self.type_display_name = data.get('type_display_name', '')
13+
self.user_display_name = data.get('user_display_name', '')
14+
self.state_token = data.get('state_token', '')
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/python
2+
3+
4+
class OTP_Device(object):
5+
def __init__(self, data):
6+
otp_device_id = data.get('id', None)
7+
self.id = int(otp_device_id) if otp_device_id is not None else None
8+
self.active = data.get('active', False)
9+
self.default = data.get('default', False)
10+
self.auth_factor_name = data.get('auth_factor_name', '')
11+
self.phone_number = data.get('phone_number', '')
12+
self.type_display_name = data.get('type_display_name', '')
13+
self.needs_trigger = data.get('needs_trigger', False)
14+
self.user_display_name = data.get('user_display_name', '')
15+
self.state_token = data.get('state_token', '')

src/onelogin/api/util/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ class Constants(object):
6666
GET_SAML_ASSERTION_URL = "https://api.%s.onelogin.com/api/1/saml_assertion"
6767
GET_SAML_VERIFY_FACTOR = "https://api.%s.onelogin.com/api/1/saml_assertion/verify_factor"
6868

69+
# Multi-Factor Authentication URLs
70+
GET_FACTORS_URL = "https://api.%s.onelogin.com/api/1/users/%s/auth_factors"
71+
ENROLL_FACTOR_URL = "https://api.%s.onelogin.com/api/1/users/%s/otp_devices"
72+
GET_ENROLLED_FACTORS_URL = "https://api.%s.onelogin.com/api/1/users/%s/otp_devices"
73+
ACTIVATE_FACTOR_URL = "https://api.%s.onelogin.com/api/1/users/%s/otp_devices/%s/trigger"
74+
VERIFY_FACTOR_URL = "https://api.%s.onelogin.com/api/1/users/%s/otp_devices/%s/verify"
75+
6976
# Invite Link URLS
7077
GENERATE_INVITE_LINK_URL = "https://api.%s.onelogin.com/api/1/invites/get_invite_link"
7178
SEND_INVITE_LINK_URL = "https://api.%s.onelogin.com/api/1/invites/send_invite_link"

0 commit comments

Comments
 (0)