Add support for Persona Identity Provider
[cascardo/ipsilon.git] / ipsilon / providers / personaidp.py
diff --git a/ipsilon/providers/personaidp.py b/ipsilon/providers/personaidp.py
new file mode 100755 (executable)
index 0000000..355726d
--- /dev/null
@@ -0,0 +1,130 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014  Ipsilon project Contributors, for licensee see COPYING
+
+from __future__ import absolute_import
+
+from ipsilon.providers.common import ProviderBase
+from ipsilon.util.plugin import PluginObject
+from ipsilon.util import config as pconfig
+from ipsilon.info.common import InfoMapping
+from ipsilon.providers.persona.auth import Persona
+from ipsilon.tools import files
+
+import json
+import M2Crypto
+import os
+
+
+class IdpProvider(ProviderBase):
+
+    def __init__(self, *pargs):
+        super(IdpProvider, self).__init__('persona', 'persona', *pargs)
+        self.mapping = InfoMapping()
+        self.page = None
+        self.basepath = None
+        self.key = None
+        self.key_info = None
+        self.description = """
+Provides Persona authentication infrastructure. """
+
+        self.new_config(
+            self.name,
+            pconfig.String(
+                'issuer domain',
+                'The issuer domain of the Persona provider',
+                'localhost'),
+            pconfig.String(
+                'idp key file',
+                'The key where the Persona key is stored.',
+                'persona.key'),
+            pconfig.List(
+                'allowed domains',
+                'List of domains this IdP is willing to issue claims for.'),
+        )
+
+    @property
+    def issuer_domain(self):
+        return self.get_config_value('issuer domain')
+
+    @property
+    def idp_key_file(self):
+        return self.get_config_value('idp key file')
+
+    @property
+    def allowed_domains(self):
+        return self.get_config_value('allowed domains')
+
+    def get_tree(self, site):
+        self.init_idp()
+        self.page = Persona(site, self)
+        # self.admin = AdminPage(site, self)
+
+        return self.page
+
+    def init_idp(self):
+        # Init IDP data
+        try:
+            self.key = M2Crypto.RSA.load_key(self.idp_key_file,
+                                             lambda *args: None)
+        except Exception, e:  # pylint: disable=broad-except
+            self._debug('Failed to init Persona provider: %r' % e)
+            return None
+
+    def on_enable(self):
+        super(IdpProvider, self).on_enable()
+        self.init_idp()
+
+
+class Installer(object):
+
+    def __init__(self, *pargs):
+        self.name = 'persona'
+        self.ptype = 'provider'
+        self.pargs = pargs
+
+    def install_args(self, group):
+        group.add_argument('--persona', choices=['yes', 'no'], default='yes',
+                           help='Configure Persona Provider')
+
+    def configure(self, opts):
+        if opts['persona'] != 'yes':
+            return
+
+        # Check storage path is present or create it
+        path = os.path.join(opts['data_dir'], 'persona')
+        if not os.path.exists(path):
+            os.makedirs(path, 0700)
+
+        keyfile = os.path.join(path, 'persona.key')
+        exponent = 0x10001
+        key = M2Crypto.RSA.gen_key(2048, exponent)
+        key.save_key(keyfile, cipher=None)
+        key_n = 0
+        for c in key.n[4:]:
+            key_n = (key_n*256) + ord(c)
+        wellknown = dict()
+        wellknown['authentication'] = '/%s/persona/SignIn/' % opts['instance']
+        wellknown['provisioning'] = '/%s/persona/' % opts['instance']
+        wellknown['public-key'] = {'algorithm': 'RS',
+                                   'e': str(exponent),
+                                   'n': str(key_n)}
+        with open(os.path.join(opts['wellknown_dir'], 'browserid'), 'w') as f:
+            f.write(json.dumps(wellknown))
+
+        # Add configuration data to database
+        po = PluginObject(*self.pargs)
+        po.name = 'persona'
+        po.wipe_data()
+        po.wipe_config_values()
+        config = {'issuer domain': opts['hostname'],
+                  'idp key file': keyfile,
+                  'allowed domains': opts['hostname']}
+        po.save_plugin_config(config)
+
+        # Update global config to add login plugin
+        po.is_enabled = True
+        po.save_enabled_state()
+
+        # Fixup permissions so only the ipsilon user can read these files
+        files.fix_user_dirs(path, opts['system_user'])