Merge remote-tracking branch 'origin/pycurl' into planetlab-4_0-branch
[plcapi.git] / PLC / Keys.py
1 import re
2
3 from PLC.Faults import *
4 from PLC.Parameter import Parameter
5 from PLC.Filter import Filter
6 from PLC.Debug import profile
7 from PLC.Table import Row, Table
8 from PLC.KeyTypes import KeyType, KeyTypes
9
10 class Key(Row):
11     """
12     Representation of a row in the keys table. To use, instantiate with a 
13     dict of values. Update as you would a dict. Commit to the database 
14     with sync().
15     """
16
17     table_name = 'keys'
18     primary_key = 'key_id'
19     join_tables = ['person_key', 'peer_key']
20     fields = {
21         'key_id': Parameter(int, "Key identifier"),
22         'key_type': Parameter(str, "Key type"),
23         'key': Parameter(str, "Key value", max = 4096),
24         'person_id': Parameter(int, "User to which this key belongs", nullok = True),
25         'peer_id': Parameter(int, "Peer to which this key belongs", nullok = True),
26         'peer_key_id': Parameter(int, "Foreign key identifier at peer", nullok = True),
27         }
28
29     # for Cache
30     class_key= 'key'
31     foreign_fields = ['key_type']
32     foreign_xrefs = []
33
34     def validate_key_type(self, key_type):
35         key_types = [row['key_type'] for row in KeyTypes(self.api)]
36         if key_type not in key_types:
37             raise PLCInvalidArgument, "Invalid key type"
38         return key_type
39
40     def validate_key(self, key):
41         # Key must not be blacklisted
42         rows = self.api.db.selectall("SELECT 1 from keys" \
43                                      " WHERE key = %(key)s" \
44                                      " AND is_blacklisted IS True",
45                                      locals())
46         if rows:
47             raise PLCInvalidArgument, "Key is blacklisted and cannot be used"
48
49         return key
50
51     def validate(self):
52         # Basic validation
53         Row.validate(self)
54
55         assert 'key' in self
56         key = self['key']
57
58         if self['key_type'] == 'ssh':
59             # Accept only SSH version 2 keys without options. From
60             # sshd(8):
61             #
62             # Each protocol version 2 public key consists of: options,
63             # keytype, base64 encoded key, comment.  The options field
64             # is optional...The comment field is not used for anything
65             # (but may be convenient for the user to identify the
66             # key). For protocol version 2 the keytype is ``ssh-dss''
67             # or ``ssh-rsa''.
68
69             good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
70             if not re.match(good_ssh_key, key, re.IGNORECASE):
71                 raise PLCInvalidArgument, "Invalid SSH version 2 public key"
72
73     def blacklist(self, commit = True):
74         """
75         Permanently blacklist key (and all other identical keys),
76         preventing it from ever being added again. Because this could
77         affect multiple keys associated with multiple accounts, it
78         should be admin only.        
79         """
80
81         assert 'key_id' in self
82         assert 'key' in self
83
84         # Get all matching keys
85         rows = self.api.db.selectall("SELECT key_id FROM keys WHERE key = %(key)s",
86                                      self)
87         key_ids = [row['key_id'] for row in rows]
88         assert key_ids
89         assert self['key_id'] in key_ids
90
91         # Keep the keys in the table
92         self.api.db.do("UPDATE keys SET is_blacklisted = True" \
93                        " WHERE key_id IN (%s)" % ", ".join(map(str, key_ids)))
94
95         # But disassociate them from all join tables
96         for table in self.join_tables:
97             self.api.db.do("DELETE FROM %s WHERE key_id IN (%s)" % \
98                            (table, ", ".join(map(str, key_ids))))
99
100         if commit:
101             self.api.db.commit()
102
103 class Keys(Table):
104     """
105     Representation of row(s) from the keys table in the
106     database.
107     """
108
109     def __init__(self, api, key_filter = None, columns = None):
110         Table.__init__(self, api, Key, columns)
111         
112         sql = "SELECT %s FROM view_keys WHERE is_blacklisted IS False" % \
113               ", ".join(self.columns)
114
115         if key_filter is not None:
116             if isinstance(key_filter, (list, tuple, set)):
117                 key_filter = Filter(Key.fields, {'key_id': key_filter})
118             elif isinstance(key_filter, dict):
119                 key_filter = Filter(Key.fields, key_filter)
120             sql += " AND (%s) %s" % key_filter.sql(api)
121
122         self.selectall(sql)