Add support for Persona Identity Provider
[cascardo/ipsilon.git] / ipsilon / providers / personaidp.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2014  Ipsilon project Contributors, for licensee see COPYING
4
5 from __future__ import absolute_import
6
7 from ipsilon.providers.common import ProviderBase
8 from ipsilon.util.plugin import PluginObject
9 from ipsilon.util import config as pconfig
10 from ipsilon.info.common import InfoMapping
11 from ipsilon.providers.persona.auth import Persona
12 from ipsilon.tools import files
13
14 import json
15 import M2Crypto
16 import os
17
18
19 class IdpProvider(ProviderBase):
20
21     def __init__(self, *pargs):
22         super(IdpProvider, self).__init__('persona', 'persona', *pargs)
23         self.mapping = InfoMapping()
24         self.page = None
25         self.basepath = None
26         self.key = None
27         self.key_info = None
28         self.description = """
29 Provides Persona authentication infrastructure. """
30
31         self.new_config(
32             self.name,
33             pconfig.String(
34                 'issuer domain',
35                 'The issuer domain of the Persona provider',
36                 'localhost'),
37             pconfig.String(
38                 'idp key file',
39                 'The key where the Persona key is stored.',
40                 'persona.key'),
41             pconfig.List(
42                 'allowed domains',
43                 'List of domains this IdP is willing to issue claims for.'),
44         )
45
46     @property
47     def issuer_domain(self):
48         return self.get_config_value('issuer domain')
49
50     @property
51     def idp_key_file(self):
52         return self.get_config_value('idp key file')
53
54     @property
55     def allowed_domains(self):
56         return self.get_config_value('allowed domains')
57
58     def get_tree(self, site):
59         self.init_idp()
60         self.page = Persona(site, self)
61         # self.admin = AdminPage(site, self)
62
63         return self.page
64
65     def init_idp(self):
66         # Init IDP data
67         try:
68             self.key = M2Crypto.RSA.load_key(self.idp_key_file,
69                                              lambda *args: None)
70         except Exception, e:  # pylint: disable=broad-except
71             self._debug('Failed to init Persona provider: %r' % e)
72             return None
73
74     def on_enable(self):
75         super(IdpProvider, self).on_enable()
76         self.init_idp()
77
78
79 class Installer(object):
80
81     def __init__(self, *pargs):
82         self.name = 'persona'
83         self.ptype = 'provider'
84         self.pargs = pargs
85
86     def install_args(self, group):
87         group.add_argument('--persona', choices=['yes', 'no'], default='yes',
88                            help='Configure Persona Provider')
89
90     def configure(self, opts):
91         if opts['persona'] != 'yes':
92             return
93
94         # Check storage path is present or create it
95         path = os.path.join(opts['data_dir'], 'persona')
96         if not os.path.exists(path):
97             os.makedirs(path, 0700)
98
99         keyfile = os.path.join(path, 'persona.key')
100         exponent = 0x10001
101         key = M2Crypto.RSA.gen_key(2048, exponent)
102         key.save_key(keyfile, cipher=None)
103         key_n = 0
104         for c in key.n[4:]:
105             key_n = (key_n*256) + ord(c)
106         wellknown = dict()
107         wellknown['authentication'] = '/%s/persona/SignIn/' % opts['instance']
108         wellknown['provisioning'] = '/%s/persona/' % opts['instance']
109         wellknown['public-key'] = {'algorithm': 'RS',
110                                    'e': str(exponent),
111                                    'n': str(key_n)}
112         with open(os.path.join(opts['wellknown_dir'], 'browserid'), 'w') as f:
113             f.write(json.dumps(wellknown))
114
115         # Add configuration data to database
116         po = PluginObject(*self.pargs)
117         po.name = 'persona'
118         po.wipe_data()
119         po.wipe_config_values()
120         config = {'issuer domain': opts['hostname'],
121                   'idp key file': keyfile,
122                   'allowed domains': opts['hostname']}
123         po.save_plugin_config(config)
124
125         # Update global config to add login plugin
126         po.is_enabled = True
127         po.save_enabled_state()
128
129         # Fixup permissions so only the ipsilon user can read these files
130         files.fix_user_dirs(path, opts['system_user'])