Class: KStor::Store

Inherits:
Object
  • Object
show all
Defined in:
lib/kstor/store.rb

Overview

Store and fetch objects in an SQLite database. rubocop:disable Metrics/MethodLength

Instance Method Summary collapse

Constructor Details

#initialize(file_path) ⇒ KStor::Store

Create a new store backed by the given SQLite database file.

Parameters:

  • file_path (String)

    path to SQLite database file



58
59
60
61
62
# File 'lib/kstor/store.rb', line 58

def initialize(file_path)
  @file_path = file_path
  @db = SQLConnection.new(file_path)
  @cache = StoreCache.new
end

Instance Method Details

#group_create(name, pubk) ⇒ Integer

Create a new group.

Note that it doesn’t store the group private key, as it must only exist in users keychains.

Parameters:

  • name (String)

    Name of the new group (must be unique in database)

  • pubk (KStor::Crypto::PublicKey)

    group public key

Returns:

  • (Integer)

    ID of the new group



143
144
145
146
147
148
149
150
# File 'lib/kstor/store.rb', line 143

def group_create(name, pubk)
  @db.execute("    INSERT INTO groups (name, pubk)\n         VALUES (?, ?)\n  EOSQL\n  @cache.forget_groups\n  @db.last_insert_row_id\nend\n", name, pubk.to_s)

#groupsArray[KStor::Model::Group]

List all groups.

Note that this list is cached in memory, so calling this method multiple times should be cheap.

Returns:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/kstor/store.rb', line 158

def groups
  @cache.groups do
    Log.debug('store: loading groups')
    rows = @db.execute("        SELECT id,\n               name,\n               pubk\n          FROM groups\n      ORDER BY name\n    EOSQL\n    rows.to_h do |r|\n      a = []\n      a << r['id']\n      a << Model::Group.new(\n        id: r['id'], name: r['name'], pubk: Crypto::PublicKey.new(r['pubk'])\n      )\n      a\n    end\n  end\nend\n")

#groups_for_secret(secret_id) ⇒ Array[KStor::Model::Group]

List of group IDs that can read this secret.

Parameters:

  • secret_id (Integer)

    ID of secret

Returns:



331
332
333
334
335
336
337
338
339
# File 'lib/kstor/store.rb', line 331

def groups_for_secret(secret_id)
  Log.debug("store: loading group IDs for secret #{secret_id}")
  rows = @db.execute("      SELECT group_id\n        FROM secret_values\n       WHERE secret_id = ?\n  EOSQL\n  rows.map { |r| r['group_id'] }\nend\n", secret_id)

#keychain_item_create(user_id, group_id, encrypted_privk) ⇒ Object

Add a group private key to a user keychain.

Parameters:

  • user_id (Integer)

    ID of an existing user

  • group_id (Integer)

    ID of an existing group

  • encrypted_privk (KStor::Crypto::ArmoredValue)

    group private key encrypted with user public key



128
129
130
131
132
133
# File 'lib/kstor/store.rb', line 128

def keychain_item_create(user_id, group_id, encrypted_privk)
  @db.execute("    INSERT INTO group_members (user_id, group_id, encrypted_privk)\n         VALUES (?, ?, ?)\n  EOSQL\nend\n", user_id, group_id, encrypted_privk.to_s)

#secret_create(author_id, encrypted_data) ⇒ Integer

Create a new secret.

Encrypted data should be a map of group_id to encrypted data for this group’s key pair as a two-value array, first metadata and then value.

Parameters:

  • author_id (Integer)

    ID of user that creates the new secret

  • encrypted_data
    Array[Hash[Integer, Array]]

    see above

    description for shape of value.

Returns:

  • (Integer)

    ID of new secret



314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/kstor/store.rb', line 314

def secret_create(author_id, encrypted_data)
  Log.debug("store: creating secret for user #{author_id}")
  @db.execute("    INSERT INTO secrets (value_author_id, meta_author_id) VALUES (?, ?)\n  EOSQL\n  secret_id = @db.last_insert_row_id\n  encrypted_data.each do |group_id, (ciphertext, encrypted_metadata)|\n    secret_value_create(secret_id, group_id, ciphertext, encrypted_metadata)\n  end\n\n  secret_id\nend\n", author_id, author_id)

#secret_delete(secret_id) ⇒ Object

Delete a secrete from database.

Parameters:

  • secret_id (Integer)

    ID of secret



388
389
390
391
392
393
394
# File 'lib/kstor/store.rb', line 388

def secret_delete(secret_id)
  Log.debug("store: delete secret ##{secret_id}")
  # Will cascade to secret_values:
  @db.execute("    DELETE FROM secrets WHERE id = ?\n  EOSQL\nend\n", secret_id)

#secret_fetch(secret_id, user_id) ⇒ KStor::Model::Secret?

Fetch one secret by it’s ID.

Parameters:

  • secret_id (Integer)

    ID of secret

  • user_id (Integer)

    ID of secret reader

Returns:

  • (KStor::Model::Secret, nil)

    A secret, or nil if secret_id was not found or user_id can’t read it.



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/kstor/store.rb', line 280

def secret_fetch(secret_id, user_id)
  Log.debug(
    "store: loading secret value ##{secret_id} for user ##{user_id}"
  )
  rows = @db.execute("       SELECT s.id,\n              s.value_author_id,\n              s.meta_author_id,\n              sv.group_id,\n              sv.ciphertext,\n              sv.encrypted_metadata\n         FROM secrets s,\n              secret_values sv,\n              group_members gm\n        WHERE gm.user_id = ?\n          AND gm.group_id = sv.group_id\n          AND sv.secret_id = ?\n          AND s.id = sv.secret_id\n  EOSQL\n  return nil if rows.empty?\n\n  secret_from_row(rows.first)\nend\n", user_id, secret_id)

#secret_setmeta(secret_id, user_id, group_encrypted_metadata) ⇒ Object

Overwrite secret metadata.

Parameters:

  • secret_id (Integer)

    ID of secret to update

  • user_id (Integer)

    ID of user that changes metadata

  • group_encrypted_metadata
    Array[Hash[Integer, KStor::Crypt::ArmoredValue]]

    map of group IDs to

    encrypted metadata.



348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/kstor/store.rb', line 348

def secret_setmeta(secret_id, user_id, )
  Log.debug("store: set metadata for secret ##{secret_id}")
  @db.execute("    UPDATE secrets SET meta_author_id = ? WHERE id = ?\n  EOSQL\n  group_encrypted_metadata.each do |group_id, encrypted_metadata|\n    @db.execute(<<-EOSQL, encrypted_metadata.to_s, secret_id, group_id)\n      UPDATE secret_values\n         SET encrypted_metadata = ?\n       WHERE secret_id = ?\n         AND group_id = ?\n    EOSQL\n  end\nend\n", user_id, secret_id)

#secret_setvalue(secret_id, user_id, group_ciphertexts) ⇒ Object

Overwrite secret value.

Parameters:

  • secret_id (Integer)

    ID of secret to update

  • user_id (Integer)

    ID of user that changes the value

  • group_ciphertexts
    Array[Hash[Integer, KStor::Crypt::ArmoredValue]]

    map of group IDs to

    encrypted values.



370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/kstor/store.rb', line 370

def secret_setvalue(secret_id, user_id, group_ciphertexts)
  Log.debug("store: set value for secret ##{secret_id}")
  @db.execute("    UPDATE secrets SET value_author_id = ? WHERE id = ?\n  EOSQL\n  group_ciphertexts.each do |group_id, ciphertext|\n    @db.execute(<<-EOSQL, ciphertext.to_s, secret_id, group_id)\n      UPDATE secret_values\n         SET ciphertext = ?\n       WHERE secret_id = ?\n         AND group_id = ?\n    EOSQL\n  end\nend\n", user_id, secret_id)

#secrets_for_user(user_id) ⇒ Array[KStor::Model::Secret]

List of all secrets that should be readable by a user.

Parameters:

  • user_id (Integer)

    ID of user that will read the secrets

Returns:



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/kstor/store.rb', line 252

def secrets_for_user(user_id)
  Log.debug("store: loading secrets for user ##{user_id}")
  rows = @db.execute("       SELECT s.id,\n              s.value_author_id,\n              s.meta_author_id,\n              sv.group_id,\n              sv.ciphertext,\n              sv.encrypted_metadata\n         FROM secrets s,\n              secret_values sv,\n              group_members gm\n        WHERE gm.user_id = ?\n          AND gm.group_id = sv.group_id\n          AND sv.secret_id = s.id\n     GROUP BY s.id\n     ORDER BY s.id, sv.group_id\n  EOSQL\n\n  rows.map { |r| secret_from_row(r) }\nend\n", user_id)

#transactionObject

Execute the given block in a database transaction.



65
66
67
# File 'lib/kstor/store.rb', line 65

def transaction(&)
  @db.transaction(&)
end

#user_by_id(user_id) ⇒ KStor::Model::User?

Lookup user by ID.

Parameters:

  • user_id (Integer)

    User ID

Returns:

  • (KStor::Model::User, nil)

    a user object instance with encrypted private data, or nil if login was not found in database.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/kstor/store.rb', line 230

def user_by_id(user_id)
  Log.debug("store: loading user by ID ##{user_id}")
  rows = @db.execute("       SELECT u.id,\n              u.login,\n              u.name,\n              u.status,\n              c.kdf_params,\n              c.pubk,\n              c.encrypted_privk,\n         FROM users u\n    LEFT JOIN users_crypto_data c ON (c.user_id = u.id)\n        WHERE u.id = ?\n  EOSQL\n  user_from_resultset(rows, include_crypto_data: true)\nend\n", user_id)

#user_by_login(login) ⇒ KStor::Model::User?

Lookup user by login.

Parameters:

  • login (String)

    User login

Returns:

  • (KStor::Model::User, nil)

    a user object instance with encrypted private data, or nil if login was not found in database.



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/kstor/store.rb', line 208

def ()
  Log.debug("store: loading user by login #{login.inspect}")
  rows = @db.execute("       SELECT u.id,\n              u.login,\n              u.name,\n              u.status,\n              c.kdf_params,\n              c.pubk,\n              c.encrypted_privk\n         FROM users u\n    LEFT JOIN users_crypto_data c ON (c.user_id = u.id)\n        WHERE u.login = ?\n  EOSQL\n  user_from_resultset(rows, include_crypto_data: true)\nend\n", )

#user_create(user) ⇒ KStor::Model::User

Create a new user in database.

Parameters:

Returns:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/kstor/store.rb', line 84

def user_create(user)
  @db.execute("    INSERT INTO users (login, name, status)\n         VALUES (?, ?, ?)\n  EOSQL\n  user_id = @db.last_insert_row_id\n  Log.debug(\"store: stored new user \#{user.login}\")\n  params = [user.kdf_params, user.pubk, user.encrypted_privk].map(&:to_s)\n  return user_id if params.any?(&:nil?)\n\n  @db.execute(<<-EOSQL, user.id, *params)\n    INSERT INTO users_crypto_data (user_id, kdf_params, pubk, encrypted_privk)\n         VALUES (?, ?, ?, ?)\n  EOSQL\n  Log.debug(\"store: stored user crypto data for \#{user.login}\")\n  @cache.forget_users\n\n  user_id\nend\n", user., user.name, 'new')

#user_update(user) ⇒ Object

Update user name, status and keychain.

Parameters:



107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/kstor/store.rb', line 107

def user_update(user)
  @db.execute("    UPDATE users SET name = ?, status = ?\n     WHERE id = ?\n  EOSQL\n  params = [user.kdf_params, user.pubk, user.encrypted_privk, user.id]\n  @db.execute(<<-EOSQL, *params)\n    UPDATE users_crypto_data SET\n           kdf_params = ?,\n           pubk = ?\n           encrypted_params = ?\n     WHERE user_id = ?\n  EOSQL\nend\n", user.name, user.status, user.id)

#usersArray[KStor::Model::User]

List all users.

Note that this list is cached in memory, so calling this method multiple times should be cheap.

Returns:



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/kstor/store.rb', line 185

def users
  @cache.users do
    Log.debug('store: loading users')
    rows = @db.execute("         SELECT u.id,\n                u.login,\n                u.name,\n                u.status,\n                c.pubk\n           FROM users u\n      LEFT JOIN users_crypto_data c ON (c.user_id = u.id)\n       ORDER BY u.login\n    EOSQL\n\n    users_from_resultset(rows)\n  end\nend\n")

#users?Boolean

True if database contains any users.

Returns:

  • (Boolean)

    false if user table is empty



72
73
74
75
76
77
78
# File 'lib/kstor/store.rb', line 72

def users?
  rows = @db.execute('SELECT count(*) AS n FROM users')
  count = Integer(rows.first['n'])
  Log.debug("store: count of users is #{count}")

  count.positive?
end