Skip to content

Commit

Permalink
Extend the map
Browse files Browse the repository at this point in the history
The map has been extended to support other attributes.
All the remaining boolean flags have been included in a new
list allowing filters to select when they can be applied.

Fix #2
  • Loading branch information
fmarco76 committed Jun 7, 2017
1 parent 98217c5 commit 2b9e475
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 11 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.rst
@@ -1 +1,2 @@
test
07 June 2017: Extend the map with all the possible parameter and add a
new list for the other using filters
24 changes: 23 additions & 1 deletion src/discourseSSO/config.py
Expand Up @@ -28,5 +28,27 @@
'name': ['givenName', 'sn'],
'username': 'username',
'external_id': 'eppn',
'email': 'mail'
'email': 'mail',
'avatar_url': 'avatar',
'bio': 'profile',
}

# Flags associated with created accounts. Possible names are:
# require_activation, admin, moderator and suppress_welcome_message.
# If the same name is repeated it is applied multiple times in the
# provided order and last accepted value will be used.
# The filter is analysed and if it match then the value is added to the
# user information sent to discourse. The filter format is key=value where
# the key is an environment attribute and the value is matched using python
# regular expression. If filter is not provided the flag is always provided
DISCOURSE_USER_FLAGS = [
{
'name': 'require_activation',
'value': 'false',
'filter': 'eppn=^my.name@my.idp$',
},
{
'name': 'admin',
'value': 'false',
},
]
23 changes: 21 additions & 2 deletions src/discourseSSO/sso.py
Expand Up @@ -27,6 +27,7 @@
import hashlib
import hmac
import urllib
import re

app = Flask(__name__)
app.config.from_object('discourseSSO.default.Config')
Expand Down Expand Up @@ -75,6 +76,7 @@ def user_authz():
:return: The redirection page to Discourse
"""
attribute_map = app.config.get('DISCOURSE_USER_MAP')
user_flag_filters = app.config.get('DISCOURSE_USER_FLAGS')
email = request.environ.get(attribute_map['email'])
external_id = request.environ.get(attribute_map['external_id'])
if not (email and external_id):
Expand All @@ -91,7 +93,8 @@ def user_authz():
"_" +
hashlib.md5(email).hexdigest()[0:4]
)

avatar_url = request.environ.get(attribute_map['avatar_url'])
bio = request.environ.get(attribute_map['bio'])
app.logger.debug('Authenticating "%s" with username "%s" and email "%s"',
name, username, email)
if 'nonce' not in session:
Expand All @@ -100,7 +103,23 @@ def user_authz():
'&name=' + name +
'&username=' + username +
'&email=' + urllib.quote(email) +
'&external_id=' + external_id)
'&external_id=' + urllib.quote(external_id))
if avatar_url:
query = query + '&avatar_url=' + urllib.quote(avatar_url)
if bio:
query = query + '&bio=' + urllib.quote(bio)
flags = {}
for user_flag in user_flag_filters:
if 'filter' in user_flag:
filter = user_flag['filter'].split('=')
reg_exp = re.compile(filter[1])
if (request.environ.get(filter[0]) and
reg_exp.match(request.environ.get(filter[0]))):
flags[user_flag['name']] = user_flag['value']
else:
flags[user_flag['name']] = user_flag['value']
for flags_name in sorted(flags.keys()):
query = query + '&' + flags_name + '=' + flags[flags_name]
app.logger.debug('Query string to return: %s', query)
query_b64 = base64.encodestring(query)
app.logger.debug('Base64 query string to return: %s', query_b64)
Expand Down
86 changes: 79 additions & 7 deletions tests/test_sso.py
Expand Up @@ -118,9 +118,10 @@ def test_authentication_generation(self):
'mI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFt'
'ZT1zYW0mdXNlcm5hbWU9%0Ac2Ftc2FtJmVt'
'YWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5'
'hbF9pZD1oZWxsbzEyMw%3D%3D%0A&sig=52'
'92265340422c9ce2d528e25d2927a2e24b4'
'81c3e91fa353516ad458d312ffd')
'hbF9pZD1oZWxsbzEyMyZhZG1pbj1m%0AYWx'
'zZQ%3D%3D%0A&sig=a8ad52d665ddf2d2d5'
'5de5d08d745f46d44a503d0b51b0273dd95'
'e1f2abe1cbd')

def test_authentication_generation_with_full_name(self):
"""Test the authentication are properly send to Discourse"""
Expand All @@ -140,12 +141,83 @@ def test_authentication_generation_with_full_name(self):
'mI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFt'
'ZT1zYW0gYmlnJnVzZXJu%0AYW1lPXNhbWJp'
'Z19iNjQyJmVtYWlsPXRlc3QlNDB0ZXN0LmN'
'vbSZleHRlcm5hbF9pZD1oZWxsbzEy%0AMw%'
'3D%3D%0A&sig=2371c654bbfbc5b322340a'
'8fc61de880147ba00cba0fd6d29a751ad12'
'e87862b')
'vbSZleHRlcm5hbF9pZD1oZWxsbzEy%0AMyZ'
'hZG1pbj1mYWxzZQ%3D%3D%0A&sig=8177ae'
'45c294212a96767cfb2208db867a14fa099'
'0bf7efb2f36dcac41d563e8')


def test_authentication_generation_with_avatar_bio(self):
"""Test the authentication are properly send to Discourse"""
with app.test_request_context('/sso/auth',
method='GET',
environ_base={
'givenName': 'sam',
'sn': '',
'username': 'samsam',
'mail': 'test@test.com',
'eppn': 'hello123',
'avatar': 'http://myAvatarURL',
'profile': 'http://myProfileURL'}
) as req:
req.session['nonce'] = 'nonce=cb68251eefb5211e58c00ff1395f0c0b'
resp = sso.user_authz()
assert resp.status_code == 302
# sso and sig are different from the one reported in
# https://meta.discourse.org/t/official-single-sign-on-for-
# discourse/13045
# This because ruby and python include new lines in different
# positions during the base64 encoding (of course they do not
# matter for the base64 but the following URLencoding and
# signature are slightly different)
assert resp.location == ('http://discuss.example.com/session/'
'sso_login?sso=bm9uY2U9Y2I2ODI1MWVlZ'
'mI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFt'
'ZT1zYW0mdXNlcm5hbWU9%0Ac2Ftc2FtJmVt'
'YWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5'
'hbF9pZD1oZWxsbzEyMyZhdmF0YXJf%0AdXJ'
'sPWh0dHAlM0EvL215QXZhdGFyVVJMJmJpbz'
'1odHRwJTNBLy9teVByb2ZpbGVVUkwmYWRta'
'W49%0AZmFsc2U%3D%0A&sig=61504842b6a'
'130d0f2d6976de814313a8df539d5e95bd9'
'32d693acbcf0b9df14')

def test_authentication_generation_with_flags(self):
"""Test the authentication are properly send to Discourse"""
with app.test_request_context('/sso/auth',
method='GET',
environ_base={
'givenName': 'sam',
'sn': '',
'username': 'samsam',
'mail': 'test@test.com',
'eppn': 'my.name@my.idp',
'avatar': 'http://myAvatarURL',
'profile': 'http://myProfileURL'}
) as req:
req.session['nonce'] = 'nonce=cb68251eefb5211e58c00ff1395f0c0b'
resp = sso.user_authz()
assert resp.status_code == 302
# sso and sig are different from the one reported in
# https://meta.discourse.org/t/official-single-sign-on-for-
# discourse/13045
# This because ruby and python include new lines in different
# positions during the base64 encoding (of course they do not
# matter for the base64 but the following URLencoding and
# signature are slightly different)
assert resp.location == ('http://discuss.example.com/session/'
'sso_login?sso=bm9uY2U9Y2I2ODI1MWVlZ'
'mI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFt'
'ZT1zYW0mdXNlcm5hbWU9%0Ac2Ftc2FtJmVt'
'YWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5'
'hbF9pZD1teS5uYW1lJTQwbXkuaWRw%0AJmF'
'2YXRhcl91cmw9aHR0cCUzQS8vbXlBdmF0YX'
'JVUkwmYmlvPWh0dHAlM0EvL215UHJvZmlsZ'
'VVS%0ATCZhZG1pbj1mYWxzZSZyZXF1aXJlX'
'2FjdGl2YXRpb249ZmFsc2U%3D%0A&sig=26'
'8beaa221824d9c5ec9df3cb85e0655e86e1'
'ba49ce516155f3f2557d7340140')

def test_error_page_403(self):
"""Test the correct error code is propagated"""
with app.test_request_context('/sso/auth',
Expand Down

0 comments on commit 2b9e475

Please sign in to comment.