An unofficial, mostly Bitwarden-compatible API server written in Ruby (Sinatra and ActiveRecord)
1#
2# Copyright (c) 2017-2018 joshua stein <jcs@jcs.org>
3#
4# Permission to use, copy, modify, and distribute this software for any
5# purpose with or without fee is hereby granted, provided that the above
6# copyright notice and this permission notice appear in all copies.
7#
8# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15#
16
17class Cipher < DBModel
18 self.table_name = "ciphers"
19 #set_primary_key "uuid"
20
21 before_create :generate_uuid_primary_key
22
23 belongs_to :user, foreign_key: :user_uuid, inverse_of: :folders
24 belongs_to :folder, foreign_key: :folder_uuid, inverse_of: :ciphers, optional: true
25 has_many :attachments, foreign_key: :cipher_uuid, dependent: :destroy
26
27 serialize :fields, JSON
28 serialize :login, JSON
29 serialize :securenote, JSON
30 serialize :card, JSON
31 serialize :identity, JSON
32 serialize :passwordhistory, JSON
33
34 TYPE_LOGIN = 1
35 TYPE_NOTE = 2
36 TYPE_CARD = 3
37 TYPE_IDENTITY = 4
38
39 def self.type_s(type)
40 case type
41 when TYPE_LOGIN
42 "login"
43 when TYPE_NOTE
44 "note"
45 when TYPE_CARD
46 "card"
47 when TYPE_IDENTITY
48 "identity"
49 else
50 type.to_s
51 end
52 end
53
54 # migrate from older style everything-in-data to separate fields
55 def migrate_data!
56 return false if !self.data
57
58 js = JSON.parse(self.data)
59 return false if !js
60
61 self.name = js.delete("Name")
62 self.notes = js.delete("Notes")
63 self.fields = js.delete("Fields")
64
65 if self.type == TYPE_LOGIN
66 js["Uris"] = [
67 { "Uri" => js["Uri"], "Match" => nil },
68 ]
69 js.delete("Uri")
70 end
71
72 # move the remaining fields into the new dedicated field based on the type
73 fmap = {
74 TYPE_LOGIN => "login",
75 TYPE_NOTE => "securenote",
76 TYPE_CARD => "card",
77 TYPE_IDENTITY => "identity",
78 }
79 self.send("#{fmap[self.type]}=", js)
80
81 self.save || raise("failed migrating #{self.inspect}")
82 true
83 end
84
85 def to_hash
86 {
87 "Id" => self.uuid,
88 "Type" => self.type,
89 "RevisionDate" => self.updated_at.strftime("%Y-%m-%dT%H:%M:%S.000000Z"),
90 "FolderId" => self.folder_uuid,
91 "Favorite" => self.favorite,
92 "OrganizationId" => nil,
93 "Attachments" => self.attachments.map(&:to_hash),
94 "OrganizationUseTotp" => false,
95 "Object" => "cipher",
96 "Name" => self.name,
97 "Notes" => self.notes,
98 "Fields" => self.fields,
99 "Login" => self.login,
100 "Card" => self.card,
101 "Identity" => self.identity,
102 "SecureNote" => self.securenote,
103 "PasswordHistory" => self.passwordhistory,
104 }
105 end
106
107 def update_from_params(params)
108 self.folder_uuid = params[:folderid]
109 self.organization_uuid = params[:organizationid]
110 self.favorite = params[:favorite]
111 self.type = params[:type].to_i
112
113 self.name = params[:name]
114 self.notes = params[:notes]
115
116 self.fields = nil
117 if params[:fields] && params[:fields].is_a?(Array)
118 self.fields = params[:fields].map{|h| h.ucfirst_hash }
119 end
120
121 case self.type
122 when TYPE_LOGIN
123 tlogin = params[:login].ucfirst_hash
124
125 if tlogin["Uris"] && tlogin["Uris"].is_a?(Array)
126 tlogin["Uris"].map!{|h| h.ucfirst_hash }
127 end
128
129 self.login = tlogin
130
131 if params[:passwordhistory].present?
132 self.passwordhistory = params[:passwordhistory].
133 map{|ph| ph.ucfirst_hash }
134 else
135 self.passwordhistory = nil
136 end
137
138 when TYPE_NOTE
139 self.securenote = params[:securenote].ucfirst_hash
140
141 when TYPE_CARD
142 self.card = params[:card].ucfirst_hash
143
144 when TYPE_IDENTITY
145 self.identity = params[:identity].ucfirst_hash
146 end
147 end
148end