Coverage for dak/import_keyring.py: 69%

135 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-08-03 16:46 +0000

1"""Imports a keyring into the database""" 

2 

3# Copyright (C) 2007 Anthony Towns <aj@erisian.com.au> 

4# Copyright (C) 2009 Mark Hymers <mhy@debian.org> 

5 

6# This program is free software; you can redistribute it and/or modify 

7# it under the terms of the GNU General Public License as published by 

8# the Free Software Foundation; either version 2 of the License, or 

9# (at your option) any later version. 

10 

11# This program is distributed in the hope that it will be useful, 

12# but WITHOUT ANY WARRANTY; without even the implied warranty of 

13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

14# GNU General Public License for more details. 

15 

16# You should have received a copy of the GNU General Public License 

17# along with this program; if not, write to the Free Software 

18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

19 

20################################################################################ 

21 

22import sys 

23 

24import apt_pkg 

25from sqlalchemy import select, sql 

26 

27from daklib.config import Config 

28from daklib.dbconn import DBConn, Fingerprint, Keyring, Uid, get_keyring 

29 

30# Globals 

31Options = None 

32 

33################################################################################ 

34 

35 

36def get_uid_info(session): 

37 byname = {} 

38 byid = {} 

39 q = session.execute(sql.text("SELECT id, uid, name FROM uid")) 

40 for keyid, uid, name in q.fetchall(): 

41 byname[uid] = (keyid, name) 

42 byid[keyid] = (uid, name) 

43 

44 return (byname, byid) 

45 

46 

47def get_fingerprint_info(session): 

48 fins = {} 

49 q = session.execute( 

50 sql.text("SELECT f.fingerprint, f.id, f.uid, f.keyring FROM fingerprint f") 

51 ) 

52 for fingerprint, fingerprint_id, uid, keyring in q.fetchall(): 

53 fins[fingerprint] = (uid, fingerprint_id, keyring) 

54 return fins 

55 

56 

57def list_uids(session, pattern): 

58 sql_pattern = f"%{pattern}%" 

59 message = "List UIDs matching pattern %s" % sql_pattern 

60 message += "\n" + ("=" * len(message)) 

61 print(message) 

62 uid_query = select(Uid).where(Uid.uid.ilike(sql_pattern)) 

63 for uid in session.scalars(uid_query): 

64 print("\nuid %s" % uid.uid) 

65 for fp in uid.fingerprint: 

66 print(" fingerprint %s" % fp.fingerprint) 

67 keyring = "unknown" 

68 if fp.keyring: 

69 keyring = fp.keyring.keyring_name 

70 print(" keyring %s" % keyring) 

71 

72 

73################################################################################ 

74 

75 

76def usage(exit_code=0): 

77 print( 

78 """Usage: dak import-keyring [OPTION]... [KEYRING] 

79 -h, --help show this help and exit. 

80 -L, --import-ldap-users generate uid entries for keyring from LDAP 

81 -U, --generate-users FMT generate uid entries from keyring as FMT 

82 -l, --list-uids STRING list all uids matching *STRING* 

83 -n, --no-action don't change database""" 

84 ) 

85 sys.exit(exit_code) 

86 

87 

88################################################################################ 

89 

90 

91def main(): 

92 global Options 

93 

94 cnf = Config() 

95 Arguments = [ 

96 ("h", "help", "Import-Keyring::Options::Help"), 

97 ("L", "import-ldap-users", "Import-Keyring::Options::Import-Ldap-Users"), 

98 ("U", "generate-users", "Import-Keyring::Options::Generate-Users", "HasArg"), 

99 ("l", "list-uids", "Import-Keyring::Options::List-UIDs", "HasArg"), 

100 ("n", "no-action", "Import-Keyring::Options::No-Action"), 

101 ] 

102 

103 for i in [ 

104 "help", 

105 "report-changes", 

106 "generate-users", 

107 "import-ldap-users", 

108 "list-uids", 

109 "no-action", 

110 ]: 

111 key = "Import-Keyring::Options::%s" % i 

112 if key not in cnf: 112 ↛ 103line 112 didn't jump to line 103

113 cnf[key] = "" 

114 

115 keyring_names = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) # type: ignore[attr-defined] 

116 

117 ### Parse options 

118 

119 Options = cnf.subtree("Import-Keyring::Options") 

120 

121 if Options["Help"]: 

122 usage() 

123 

124 ### Initialise 

125 session = DBConn().session() 

126 

127 if Options["List-UIDs"]: 127 ↛ 128line 127 didn't jump to line 128 because the condition on line 127 was never true

128 list_uids(session, Options["List-UIDs"]) 

129 sys.exit(0) 

130 

131 if len(keyring_names) != 1: 131 ↛ 132line 131 didn't jump to line 132 because the condition on line 131 was never true

132 usage(1) 

133 

134 ### Keep track of changes made 

135 changes = [] # (uid, changes strings) 

136 

137 ### Cache all the existing fingerprint entries 

138 db_fin_info = get_fingerprint_info(session) 

139 

140 ### Parse the keyring 

141 

142 keyringname = keyring_names[0] 

143 keyring = get_keyring(keyringname, session) 

144 if not keyring: 144 ↛ 145line 144 didn't jump to line 145 because the condition on line 144 was never true

145 print("E: Can't load keyring %s from database" % keyringname) 

146 sys.exit(1) 

147 

148 keyring.load_keys(keyringname) 

149 

150 ### Generate new uid entries if they're needed (from LDAP or the keyring) 

151 if Options["Generate-Users"]: 151 ↛ 155line 151 didn't jump to line 155 because the condition on line 151 was always true

152 _, desuid_byid = keyring.generate_users_from_keyring( 

153 Options["Generate-Users"], session 

154 ) 

155 elif Options["Import-Ldap-Users"]: 

156 _, desuid_byid = keyring.import_users_from_ldap(session) 

157 else: 

158 desuid_byid = {} 

159 

160 ### Cache all the existing uid entries 

161 (db_uid_byname, db_uid_byid) = get_uid_info(session) 

162 

163 ### Update full names of applicable users 

164 for keyid in desuid_byid.keys(): 

165 uid = (keyid, desuid_byid[keyid][0]) 

166 name = desuid_byid[keyid][1] 

167 oname = db_uid_byid[keyid][1] 

168 if name and oname != name: 

169 changes.append((uid[1], "Full name: %s" % (name))) 

170 session.execute( 

171 sql.text("UPDATE uid SET name = :name WHERE id = :keyid"), 

172 {"name": name, "keyid": keyid}, 

173 ) 

174 

175 # The fingerprint table (fpr) points to a uid and a keyring. 

176 # If the uid is being decided here (ldap/generate) we set it to it. 

177 # Otherwise, if the fingerprint table already has a uid (which we've 

178 # cached earlier), we preserve it. 

179 # Otherwise we leave it as None 

180 

181 fpr = {} 

182 for z in keyring.keys.keys(): 

183 keyid = db_uid_byname.get(keyring.keys[z].get("uid", None), [None])[0] 

184 if keyid is None: 184 ↛ 185line 184 didn't jump to line 185 because the condition on line 184 was never true

185 keyid = db_fin_info.get(keyring.keys[z]["fingerprints"][0], [None])[0] 

186 for y in keyring.keys[z]["fingerprints"]: 

187 fpr[y] = (keyid, keyring.keyring_id) 

188 

189 # For any keys that used to be in this keyring, disassociate them. 

190 # We don't change the uid, leaving that for historical info; if 

191 # the id should change, it'll be set when importing another keyring. 

192 

193 for f, (u, fid, kr) in db_fin_info.items(): 

194 if kr != keyring.keyring_id: 194 ↛ 197line 194 didn't jump to line 197 because the condition on line 194 was always true

195 continue 

196 

197 if f in fpr: 

198 continue 

199 

200 changes.append((db_uid_byid.get(u, [None])[0], "Removed key: %s" % (f))) 

201 session.execute( 

202 sql.text( 

203 """UPDATE fingerprint 

204 SET keyring = NULL 

205 WHERE id = :fprid""" 

206 ), 

207 {"fprid": fid}, 

208 ) 

209 

210 # For the keys in this keyring, add/update any fingerprints that've 

211 # changed. 

212 

213 for f in fpr: 

214 newuid = fpr[f][0] 

215 newuiduid = db_uid_byid.get(newuid, [None])[0] 

216 

217 (olduid, oldfid, oldkid) = db_fin_info.get(f, [-1, -1, -1]) 

218 

219 if olduid is None: 219 ↛ 220line 219 didn't jump to line 220 because the condition on line 219 was never true

220 olduid = -1 

221 

222 if oldkid is None: 222 ↛ 223line 222 didn't jump to line 223 because the condition on line 222 was never true

223 oldkid = -1 

224 

225 if oldfid == -1: 

226 changes.append((newuiduid, "Added key: %s" % (f))) 

227 fp = Fingerprint() 

228 fp.fingerprint = f 

229 fp.keyring_id = keyring.keyring_id 

230 if newuid: 230 ↛ 233line 230 didn't jump to line 233 because the condition on line 230 was always true

231 fp.uid_id = newuid 

232 

233 session.add(fp) 

234 session.flush() 

235 

236 else: 

237 if newuid and olduid != newuid and olduid == -1: 237 ↛ 238line 237 didn't jump to line 238 because the condition on line 237 was never true

238 changes.append((newuiduid, "Linked key: %s" % f)) 

239 changes.append((newuiduid, " (formerly unowned)")) 

240 session.execute( 

241 sql.text("UPDATE fingerprint SET uid = :uid WHERE id = :fpr"), 

242 {"uid": newuid, "fpr": oldfid}, 

243 ) 

244 

245 # Don't move a key from a keyring with a higher priority to a lower one 

246 if oldkid != keyring.keyring_id: 246 ↛ 213line 246 didn't jump to line 213 because the condition on line 246 was always true

247 movekey = False 

248 if oldkid == -1: 248 ↛ 249line 248 didn't jump to line 249 because the condition on line 248 was never true

249 movekey = True 

250 else: 

251 oldkeyring = session.get(Keyring, oldkid) 

252 if oldkeyring is None: 252 ↛ 253line 252 didn't jump to line 253 because the condition on line 252 was never true

253 print("ERROR: Cannot find old keyring with id %s" % oldkid) 

254 sys.exit(1) 

255 

256 if oldkeyring.priority < keyring.priority: 256 ↛ 257line 256 didn't jump to line 257 because the condition on line 256 was never true

257 movekey = True 

258 

259 # Only change the keyring if it won't result in a loss of permissions 

260 if movekey: 260 ↛ 261line 260 didn't jump to line 261 because the condition on line 260 was never true

261 session.execute( 

262 sql.text( 

263 """UPDATE fingerprint 

264 SET keyring = :keyring 

265 WHERE id = :fpr""" 

266 ), 

267 {"keyring": keyring.keyring_id, "fpr": oldfid}, 

268 ) 

269 

270 session.flush() 

271 

272 else: 

273 # movekey is only False if the old keyring was looked up 

274 assert oldkeyring is not None 

275 print( 

276 "Key %s exists in both %s and %s keyrings. Not demoting." 

277 % (f, oldkeyring.keyring_name, keyring.keyring_name) 

278 ) 

279 

280 # All done! 

281 if Options["No-Action"]: 281 ↛ 282line 281 didn't jump to line 282 because the condition on line 281 was never true

282 session.rollback() 

283 else: 

284 session.commit() 

285 

286 # Print a summary 

287 changesd = {} 

288 for k, v in changes: 

289 if k not in changesd: 

290 changesd[k] = "" 

291 changesd[k] += " %s\n" % (v) 

292 

293 for k in sorted(changesd): 

294 print("%s\n%s\n" % (k, changesd[k]))