Fix slice tags
[plcapi.git] / PLC / Keys.py
1 import re
2 import random
3 import string
4 from types import StringTypes
5
6 from PLC.Faults import *
7 from PLC.Parameter import Parameter
8 from PLC.Filter import Filter
9 from PLC.Debug import profile
10 from PLC.Table import Row, Table
11 from PLC.KeyTypes import KeyType, KeyTypes
12 from PLC.Storage.AlchemyObject import AlchemyObj
13
14 class Key(AlchemyObj):
15     """
16     Representation of a row in the keys table. To use, instantiate with a
17     dict of values. Update as you would a dict. Commit to the database
18     with sync().
19     """
20
21     tablename = 'keys'
22     fields = {
23         'key_id': Parameter(str, "Key identifier", primary_key=True),
24         'key_type': Parameter(str, "Key type"),
25         'key': Parameter(str, "Key string", max = 4096), # backwards compatibility
26         'person_id': Parameter(int, "User to which this key belongs", nullok = True),
27         'is_blacklisted': Parameter(bool, "Is the key blacklisted", default=False),
28         'name': Parameter(str, "Key name"),
29         'peer_id': Parameter(int, "Peer to which this key belongs", nullok = True),
30         'peer_key_id': Parameter(int, "Foreign key identifier at peer", nullok = True),
31         }
32
33     def sync(self, commit = True, validate = True):
34         from PLC.Persons import Person
35         # sync the nova record and the plc record
36         assert 'key' in self
37         AlchemyObj.sync(self, commit=commit, validate=validate)
38         persons = Person().select(filter={'person_id': self['person_id']})
39         if not persons:
40             raise PLCInvalidArgument, "Invalid person id" 
41         person = persons[0]
42         if 'key_id' not in self:
43             rand = ''.join(random.choice(string.ascii_letters) for x in range(5))
44             self['name'] = person.email.split('@')[0] + rand
45             nova_key = {
46                 'public_key': self['key'],
47                 'name': self['name']
48             }
49             self.object = self.api.client_shell.nova.keypairs.create(**nova_key)
50             AlchemyObj.insert(self, dict(self))
51             key = Key().select(filter={'name': self['name']})[0]
52             self['key_id'] = key.key_id 
53         else:
54             # nova has no way to update existing keys. Just remove the old
55             # key and add a new one 
56             nova_key = {
57                 'public_key': self['key'],
58                 'name': self['name']
59             }    
60             self.api.client_shell.nova.keypairs.delete(self['name'])
61             self.object = self.api.client_shell.nova.keypairs.create(**nova_key)      
62             AlchemyObj.update(self, {'key_id': self['key_id']}, dict(self))
63             
64     def delete(self):
65         assert 'key_id' in self
66         assert 'name' in self
67         self.api.client_shell.nova.keypairs.delete(self['name'])
68         AlchemyObj.delete(self, filter={'key_id': self['key_id']}) 
69         
70
71     def validate_key_type(self, key_type):
72         exists = KeyTypes(self.api, key_type)
73         if not exists:
74             raise PLCInvalidArgument, "Invalid key type"
75         return key_type
76
77     def validate_key(self, key):
78         # Key must not be blacklisted
79         keys = Keys(self.api, {'key': key, 'is_blacklisted': True})
80         if keys:
81             raise PLCInvalidArgument, "Key is blacklisted and cannot be used"
82
83         return key
84
85     def validate(self):
86         # Basic validation
87         AlchemyObj.validate(self)
88
89         assert 'key' in self
90         key = self['key']
91
92         if self['key_type'] == 'ssh':
93             # Accept only SSH version 2 keys without options. From
94             # sshd(8):
95             #
96             # Each protocol version 2 public key consists of: options,
97             # keytype, base64 encoded key, comment.  The options field
98             # is optional...The comment field is not used for anything
99             # (but may be convenient for the user to identify the
100             # key). For protocol version 2 the keytype is ``ssh-dss''
101             # or ``ssh-rsa''.
102
103             good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
104             if not re.match(good_ssh_key, key, re.IGNORECASE):
105                 raise PLCInvalidArgument, "Invalid SSH version 2 public key"
106
107     def blacklist(self, commit = True):
108         """
109         Permanently blacklist key (and all other identical keys),
110         preventing it from ever being added again. Because this could
111         affect multiple keys associated with multiple accounts, it
112         should be admin only.
113         """
114
115         assert 'key_id' in self
116         assert 'key' in self
117
118         AlchemyObj.updatedb(self, filter={'key': self['key']}, values={'is_blacklisted': True}) 
119
120
121 class Keys(list):
122     """
123     Representation of row(s) from the keys table in the
124     database.
125     """
126
127     def __init__(self, api, key_filter = None, columns = None):
128         self.api = api
129
130         if not key_filter:
131             keys = Key().select()
132         elif isinstance(key_filter, dict):
133             keys = Key().select(filter=key_filter) 
134         elif isinstance(key_filter, StringTypes):
135             keys = Key().select(filter={'name': key_filter})
136         elif isinstance(key_filter, int):
137             keys = Key().select(filter={'key_id': key_filter})
138         elif isinstance(key_filter, (list, tuple, set)):
139             ints = filter(lambda x: isinstance(x, (int, long)), key_filter)
140             strs = filter(lambda x: isinstance(x, StringTypes), key_filter)
141             key_filter = {'key_id': ints, 'name': strs}
142             keys = Key().select(filter=key_filter)  
143         else:
144             raise PLCInvalidArgument, "Wrong key filter %s" % key_filter  
145             
146         for key in keys:
147             key = Key(api, object=key, columns=columns)
148             self.append(key)   
149