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