Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions docs/user/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@ OAuth Authentication
--------------------

OAuth2 password flow is currently supported out of box, utilizing the default legacy mobile authenticator. Requires BasicAuth to
be enabled on the server for the first step.
be enabled on the server for the first step to grab the legacy mobile secret.

>>> from pysnc import ServiceNowClient, ServiceNowOAuth2
>>> client = ServiceNowClient(self.c.server, ServiceNowOAuth2('admin', 'password'))
>>> client = ServiceNowClient(server_url, ServiceNowOAuth2(username, password))

This will retrieve and store an OAuth2 auth and refresh token, sending your auth token with every request and dropping your user password.

If this sleeps for a long enough time, even your refresh token will expire and you must re-auth.
Alternatively you can specify your own client and you do not require basic auth:

>>> client = ServiceNowClient(server_url, ServiceNowOAuth2(username, password, client_id, client_secret))

If this sleeps for a long enough time your refresh token will expire and you must re-auth.

Requests Authentication
-----------------------
Expand Down
6 changes: 5 additions & 1 deletion docs/user/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Installation of PySNC
=====================

For now:
As such:

$ pip install pysnc

Or:

$ python setup.py install
2 changes: 1 addition & 1 deletion pysnc/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__title__ = 'pysnc'
__version__ = '1.0.2'
__version__ = '1.0.3'

25 changes: 16 additions & 9 deletions pysnc/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@

class ServiceNowOAuth2(object):

def __init__(self, username, password=None, client_id='3e57bb02663102004d010ee8f561307a'):
def __init__(self, username, password=None, client_id='3e57bb02663102004d010ee8f561307a', client_secret=None):
"""
Password flow authentication using 'legacy mobile'

:param username: The user name to authenticate with
:param password: The user's password
:param client_id: The ID of the provider
:param client_secret: Secret for the given provider (client_id)
"""
if isinstance(username, (tuple, list)):
self.__username = username[0]
Expand All @@ -20,6 +21,15 @@ def __init__(self, username, password=None, client_id='3e57bb02663102004d010ee8f
self.__username = username
self.__password = password
self.client_id = client_id
self.__secret = client_secret

def _get_mobile_secret(self, instance):
secret_url = '%s/api/now/mobileapp/plugin/secret' % instance
r = requests.get(secret_url, auth=(self.__username, self.__password))
if r.status_code != 200:
# likely basic auth doesn't work, meaning password flow likely wont work
return None
return r.json()['result']['secret']

def authenticate(self, instance):
"""
Expand All @@ -28,18 +38,15 @@ def authenticate(self, instance):
try:
from oauthlib.oauth2 import LegacyApplicationClient
from requests_oauthlib import OAuth2Session
secret_url = '%s/api/now/mobileapp/plugin/secret' % instance
r = requests.get(secret_url, auth=(self.__username, self.__password))
if r.status_code != 200:
# likely basic auth doesn't work, meaning password flow likely wont work
raise AuthenticationException(r.text)
client_secret = r.json()['result']['secret']

if self.__secret is None:
self.__secret = self._get_mobile_secret(instance)

oauth = OAuth2Session(client=LegacyApplicationClient(client_id=self.client_id))
oauth.fetch_token(token_url='%s/oauth_token.do' % instance,
username=self.__username, password=self.__password, client_id=self.client_id,
client_secret=client_secret)
self.__password = None # no longer need this.
client_secret=self.__secret)
self.__password = None # no longer need this.
return oauth
except ImportError:
raise AuthenticationException('Install dependency requests-oauthlib')
Expand Down
2 changes: 1 addition & 1 deletion pysnc/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(self, message, status_code=None):
message = message['error']['message']
self.detail = message['error']['detail']
self.status = message['error']['status']'''
super(RestException, self).__init__(self, message)
super(RestException, self).__init__(self, "%s - %s" % (status_code, message))
self.status_code = status_code


Expand Down
3 changes: 3 additions & 0 deletions pysnc/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ def fields(self):
c = self._current()
if c:
return list(c.keys())
else:
if len(self.__results) > 0:
return list(self.__results[0].keys())
return None

@fields.setter
Expand Down
2 changes: 0 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
# 5 - Production/Stable
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Operating System :: OS Independent',
Expand Down
11 changes: 10 additions & 1 deletion test/test_snc_api_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,21 @@ def test_field_getter(self):
gr.fields = ['sys_id']
self.assertEquals(gr.fields, ['sys_id'])

def test_field_all(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('sys_user')
self.assertIsNone(gr.fields)
gr.query()
self.assertIsNotNone(gr.fields)

def test_field_getter_query(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('sys_user')
self.assertEquals(gr.fields, None)
gr.limit = 1
gr.query()
self.assertEquals(gr.fields, None)
self.assertIsNotNone(gr.fields)
self.assertGreater(len(gr.fields), 10)
gr.next()
print(gr.fields)
self.assertGreater(len(gr.fields), 10)
Expand Down Expand Up @@ -159,3 +167,4 @@ def test_attrs_changes(self):
self.assertEquals(gr.get_element('sys_id').changes(), False)
gr.sys_id = '1234'
self.assertEquals(gr.get_element('sys_id').changes(), True)

2 changes: 1 addition & 1 deletion test/test_snc_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_oauth(self):
secret_url = '%s/api/now/mobileapp/plugin/secret' % server
r = requests.get(secret_url, auth=creds)
if r.status_code != 200:
raise 'couldnt get secret'
raise Exception('couldnt get secret')
secret = r.json()['result']['secret']
self.assertIsNotNone(secret)

Expand Down
17 changes: 10 additions & 7 deletions test/test_snc_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ def test_pandas_order_cols(self):

data = gr.to_pandas()
print(data)
self.assertEquals(list(data.keys()), ['sys_id', 'short_description', 'state__value', 'state__display'])
self.assertListEqual(list(data.keys()), ['sys_id', 'short_description', 'state__value', 'state__display'])
data = gr.to_pandas(mode='display')
print(data)
self.assertEquals(list(data.keys()), ['sys_id', 'short_description', 'state'])
self.assertListEqual(list(data.keys()), ['sys_id', 'short_description', 'state'])
data = gr.to_pandas(columns=['jack', 'jill', 'hill'], mode='display')
print(data)
self.assertEquals(list(data.keys()), ['jack', 'jill', 'hill'])
self.assertListEqual(list(data.keys()), ['jack', 'jill', 'hill'])


def test_serialize_all_batch(self):
Expand Down Expand Up @@ -119,10 +119,10 @@ def test_serialize_changes(self):
gr.next()
data = gr.serialize()
self.assertIsNotNone(data)
self.assertEquals(list(data.keys()), ['sys_id', 'short_description', 'state'])
self.assertEquals(list(gr.serialize(changes_only=True).keys()), [])
self.assertListEqual(list(data.keys()), ['sys_id', 'short_description', 'state'])
self.assertListEqual(list(gr.serialize(changes_only=True).keys()), [])
gr.short_description = 'new'
self.assertEquals(list(gr.serialize(changes_only=True).keys()), ['short_description'])
self.assertListEqual(list(gr.serialize(changes_only=True).keys()), ['short_description'])

def test_serialize(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
Expand Down Expand Up @@ -158,7 +158,10 @@ def test_str(self):
gr.intfield = 5
data = str(gr)
self.assertIsNotNone(data)
self.assertEquals(data, "some_table({'strfield': 'my string', 'intfield': 5})")
# dict is unordered, so do some contains checks
self.assertTrue(data.startswith('some_table'))
self.assertTrue('my string' in data)
self.assertTrue('intfield' in data)



2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27,py37
envlist = py37

[testenv]

Expand Down