changeset 4883:6586038c07d3

Added sreg and teams extensions for OpenID relying party authentication
author Rowan Kerr <rowan@stasis.org>
date Tue, 26 May 2009 09:44:49 -0400
parents c2002dd6019f
children 73f4fbb892b1
files MoinMoin/auth/openidrp_ext/__init__.py MoinMoin/auth/openidrp_ext/openidrp_sreg.py MoinMoin/auth/openidrp_ext/openidrp_teams.py
diffstat 3 files changed, 226 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/openidrp_ext/__init__.py	Tue May 26 09:44:49 2009 -0400
@@ -0,0 +1,8 @@
+# -*- coding: iso-8859-1 -*-
+"""
+MoinMoin OpenID Relying Party Extensions
+
+@copyright: 2007-20099 Canonical, Inc.
+@license: GNU GPL, see COPYING for details.
+"""
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/openidrp_ext/openidrp_sreg.py	Tue May 26 09:44:49 2009 -0400
@@ -0,0 +1,94 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Simple Registration Extension for OpenID authorization
+
+    @copyright: 2009 Canonical, Inc.
+    @license: GNU GPL, see COPYING for details.
+"""
+#from MoinMoin.util.moinoid import MoinOpenIDStore
+from MoinMoin import user
+from MoinMoin.auth import BaseAuth
+from MoinMoin.auth.openidrp import OpenIDAuth
+#from openid.consumer import consumer
+#from openid.yadis.discover import DiscoveryFailure
+#from openid.fetchers import HTTPFetchingError
+#from MoinMoin.widget import html
+#from MoinMoin.auth import CancelLogin, ContinueLogin
+#from MoinMoin.auth import MultistageFormLogin, MultistageRedirectLogin
+#from MoinMoin.auth import get_multistage_continuation_url
+
+from openid.extensions.sreg import *
+from MoinMoin import i18n
+from datetime import datetime, timedelta
+from pytz import timezone
+import pytz
+
+#class OpenIDSREGAuth(OpenIDAuth):
+#    login_inputs = ['openid_identifier']
+#    name = 'openid'
+#    logout_possible = True
+#    auth_attribs = ['name', 'email', 'aliasname', 'language', 'tz_offset']
+
+OpenIDAuth.auth_attribs = ['name', 'email', 'aliasname', 'language', 'tz_offset']
+
+def openidrp_sreg_modify_request(oidreq, cfg):
+    oidreq.addExtension(SRegRequest(required=cfg.openidrp_sreg_required,
+                                    optional=cfg.openidrp_sreg_optional))
+    return
+
+def openidrp_sreg_create_user(info, u, cfg):
+    sreg = _openidrp_sreg_extract_values(info)
+    if sreg and sreg[cfg.openidrp_sreg_username_field] != '':
+        u.name = sreg[cfg.openidrp_sreg_username_field]
+    return u
+
+def openidrp_sreg_update_user(info, u, cfg):
+    sreg = _openidrp_sreg_extract_values(info)
+    if sreg:
+        u.name = sreg[cfg.openidrp_sreg_username_field]
+        if sreg['email'] != '':
+            u.email = sreg['email']
+        if sreg['language'] != '':
+            u.language = sreg['language']
+        if sreg['timezone'] != '':
+            u.tz_offset = sreg['timezone']
+        if sreg['fullname'] != '':
+            u.fullname = sreg['fullname']
+    return
+
+def _openidrp_sreg_extract_values(info):
+    # Pull SREG data here instead of asking user
+    sreg_resp = SRegResponse.fromSuccessResponse(info)
+    sreg = {'nickname': '', 'email': '', 'fullname': '',
+            'dob': '0000-00-00', 'gender': '', 'postcode': '',
+            'country': '', 'language': '', 'timezone' : ''}
+    if sreg_resp:
+        if sreg_resp.get('nickname'):
+            sreg['nickname'] = sreg_resp.get('nickname')
+        if sreg_resp.get('fullname'):
+            sreg['fullname'] = sreg_resp.get('fullname')
+        if sreg_resp.get('email'):
+            sreg['email'] = sreg_resp.get('email')
+        # Language must be a valid value
+        # check the MoinMoin list, or restrict to first 2 chars
+        if sreg_resp.get('language'):
+            # convert unknown codes to 2 char format
+            langs = i18n.wikiLanguages().items()
+            sreg['language'] = sreg_resp.get('language')
+            lang_found = False
+            for lang in langs:
+                if lang[0] == sreg['language']:
+                    lang_found = True
+            if not lang_found:
+                if langs[sreg['language'][0:2]]:
+                    sreg['language'] = sreg['language'][0:2]
+        # Timezone must be converted to offset in seconds
+        if sreg_resp.get('timezone'):
+            user_tz = timezone(sreg_resp.get('timezone').encode('ascii'))
+            if user_tz:
+                user_utcoffset = user_tz.utcoffset(datetime.utcnow())
+                sreg['timezone'] = user_utcoffset.days * 24 * 60 * 60 + user_utcoffset.seconds
+            else:
+                sreg['timezone'] = 0
+    return sreg
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/openidrp_ext/openidrp_teams.py	Tue May 26 09:44:49 2009 -0400
@@ -0,0 +1,124 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Launchpad Teams Extension for OpenID authorization
+
+    @copyright: 2009 Canonical, Inc.
+    @license: GNU GPL, see COPYING for details.
+"""
+import re
+import logging
+import copy
+
+#from MoinMoin.util.moinoid import MoinOpenIDStore
+from MoinMoin import user
+from MoinMoin.auth import BaseAuth
+from MoinMoin.auth.openidrp import OpenIDAuth
+#OpenIDSREGAuth
+#from openid.consumer import consumer
+#from openid.yadis.discover import DiscoveryFailure
+#from openid.fetchers import HTTPFetchingError
+#from MoinMoin.widget import html
+#from MoinMoin.auth import CancelLogin, ContinueLogin
+#from MoinMoin.auth import MultistageFormLogin, MultistageRedirectLogin
+#from MoinMoin.auth import get_multistage_continuation_url
+
+from openid.extensions.teams import TeamsRequest, TeamsResponse, supportsTeams
+from MoinMoin import wikiutil
+from MoinMoin.PageEditor import PageEditor, conflict_markers
+from MoinMoin.Page import Page
+
+#class OpenIDTeamsAuth(OpenIDSREGAuth):
+#    login_inputs = ['openid_identifier']
+#    name = 'openid'
+#    logout_possible = True
+#    #auth_attribs = ['name', 'email', 'aliasname', 'language', 'tz_offset']
+
+def openidrp_teams_modify_request(oidreq, cfg):
+    # Request Launchpad teams information, if configured
+    # should also check supportsTeams() result
+    #if teams_extension_avail and len(cfg.openidrp_authorized_teams) > 0:
+    if len(cfg.openidrp_authorized_teams) > 0:
+        oidreq.addExtension(TeamsRequest(cfg.openidrp_authorized_teams))
+    return
+
+def openidrp_teams_create_user(info, u, cfg):
+    # Check for Launchpad teams data in response
+    teams = None
+    #if teams_extension_avail and len(cfg.openidrp_authorized_teams) > 0:
+    teams_response = TeamsResponse.fromSuccessResponse(info)
+    teams = teams_response.is_member
+    if teams:
+        _save_teams_acl(u, teams)
+    return u
+
+def openidrp_teams_update_user(info, u, cfg):
+    teams = None
+    teams_response = TeamsResponse.fromSuccessResponse(info)
+    teams = teams_response.is_member
+    if teams:
+        _save_teams_acl(u, teams, cfg)
+    return
+
+# Take a list of Launchpad teams and add the user to the ACL pages
+# ACL group names cannot have "-" in them, although team names do.
+def _save_teams_acl(u, teams, cfg):
+    logging.log(logging.INFO, "running save_teams_acl...")
+
+    # remove any teams the user is no longer in
+    if not hasattr(u, 'teams'):
+        u.teams = []
+    logging.log(logging.INFO, "old teams: " + str(u.teams)
+        + "  new teams: " + str(teams))
+
+    for t in u.teams:
+        if not t in teams:
+            logging.log(logging.INFO, "remove user from team: " + t)
+            team = t.strip().replace("-", "")
+            _remove_user_from_team(u, team, cfg)
+
+    for t in teams:
+        team = t.strip().replace("-", "")
+        if not team:
+            continue
+        logging.log(logging.INFO, "Launchpad team: "  + team)
+        _add_user_to_team(u, team, cfg)
+
+    u.teams = teams
+    u.save()
+
+def _add_user_to_team(u, team, cfg):
+    # use admin account to create or edit ACL page
+    # http://moinmo.in/MoinDev/CommonTasks
+    acl_request = u._request
+    acl_request.user = user.User(acl_request, None, cfg.openidrp_acl_admin)
+    pe = PageEditor(acl_request, team + cfg.openidrp_acl_page_postfix)
+    acl_text = pe.get_raw_body()
+    logging.log(logging.INFO, "ACL Page content: " + acl_text)
+    # make sure acl command is first line of document
+    # only the admin user specified in wikiconfig should
+    # be allowed to change these acl files
+    if not acl_text or acl_text == "" or acl_text[0] != "#":
+        acl_text = "#acl Known:read All:\n" + acl_text
+    # does ACL want uid, name, username, auth_username?
+    p = re.compile(ur"^ \* %s" % u.name, re.MULTILINE)
+    if not p.search(acl_text):
+        logging.log(logging.INFO, "did not find user %s in acl, adding..." % u.name)
+        acl_text += u" * %s\n" % u.name
+        pe.saveText(acl_text, 0)
+
+def _remove_user_from_team(u, team, cfg):
+    acl_request = u._request
+    acl_request.user = user.User(acl_request, None, cfg.openidrp_acl_admin)
+    pe = PageEditor(acl_request, team + cfg.openidrp_acl_page_postfix)
+    acl_text = pe.get_raw_body()
+    logging.log(logging.INFO, "ACL Page content: " + acl_text)
+    # does ACL want uid, name, username, auth_username?
+    p = re.compile(ur"^ \* %s" % u.name, re.MULTILINE)
+    if p.search(acl_text):
+        logging.log(logging.INFO, "found user %s in acl, removing..." % u.name)
+        acl_text = acl_text.replace(" * %s\n" % u.name, "")
+        try:
+            pe.saveText(acl_text, 0)
+        except PageEditor.EmptyPage:
+            pe.deletePage()
+