github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/model/bitwarden/organization.go (about) 1 package bitwarden 2 3 import ( 4 "errors" 5 6 "github.com/cozy/cozy-stack/model/bitwarden/settings" 7 "github.com/cozy/cozy-stack/model/instance" 8 "github.com/cozy/cozy-stack/pkg/consts" 9 "github.com/cozy/cozy-stack/pkg/couchdb" 10 "github.com/cozy/cozy-stack/pkg/couchdb/mango" 11 "github.com/cozy/cozy-stack/pkg/crypto" 12 "github.com/cozy/cozy-stack/pkg/metadata" 13 ) 14 15 // OrgMemberStatus is a type for the status of an organization member 16 type OrgMemberStatus int 17 18 const ( 19 // OrgMemberInvited is used when the member is invited but has not yet 20 // accepted the invitation. 21 OrgMemberInvited OrgMemberStatus = 0 22 // OrgMemberAccepted is used when the member is accepted but the owner has 23 // not yet confirmed that the fingerprint is OK. 24 OrgMemberAccepted OrgMemberStatus = 1 25 // OrgMemberConfirmed is used when the member is confirmed, and has access 26 // to the organization key to decrypt/encrypt ciphers. 27 OrgMemberConfirmed OrgMemberStatus = 2 28 ) 29 30 // OrgMember is a struct for describing a member of an organization. 31 type OrgMember struct { 32 UserID string `json:"user_id"` 33 Email string `json:"email"` 34 Name string `json:"name"` 35 OrgKey string `json:"key,omitempty"` // The organization key encrypted with the public key of the user 36 Status OrgMemberStatus `json:"status"` 37 Owner bool `json:"owner,omitempty"` 38 ReadOnly bool `json:"read_only,omitempty"` 39 } 40 41 // Collection is used to regroup ciphers. 42 type Collection struct { 43 DocID string `json:"_id"` 44 Name string `json:"name"` 45 } 46 47 // ID returns the collection identifier 48 func (c *Collection) ID() string { return c.DocID } 49 50 // Organization is used to make collections of ciphers and can be used for 51 // sharing them with other users with cryptography mechanisms. 52 type Organization struct { 53 CouchID string `json:"_id,omitempty"` 54 CouchRev string `json:"_rev,omitempty"` 55 Name string `json:"name"` 56 Members map[string]OrgMember `json:"members"` // the keys are the instances domains 57 Collection Collection `json:"defaultCollection"` 58 Metadata metadata.CozyMetadata `json:"cozyMetadata"` 59 } 60 61 // ID returns the organization identifier 62 func (o *Organization) ID() string { return o.CouchID } 63 64 // Rev returns the organization revision 65 func (o *Organization) Rev() string { return o.CouchRev } 66 67 // SetID changes the organization identifier 68 func (o *Organization) SetID(id string) { o.CouchID = id } 69 70 // SetRev changes the organization revision 71 func (o *Organization) SetRev(rev string) { o.CouchRev = rev } 72 73 // DocType returns the organization document type 74 func (o *Organization) DocType() string { return consts.BitwardenOrganizations } 75 76 // Clone implements couchdb.Doc 77 func (o *Organization) Clone() couchdb.Doc { 78 cloned := *o 79 cloned.Members = make(map[string]OrgMember, len(o.Members)) 80 for k, v := range o.Members { 81 cloned.Members[k] = v 82 } 83 return &cloned 84 } 85 86 // FindCiphers returns the ciphers for this organization. 87 func (o *Organization) FindCiphers(inst *instance.Instance) ([]*Cipher, error) { 88 var ciphers []*Cipher 89 req := &couchdb.FindRequest{ 90 UseIndex: "by-organization-id", 91 Selector: mango.Equal("organization_id", o.CouchID), 92 Limit: 1000, 93 } 94 err := couchdb.FindDocs(inst, consts.BitwardenCiphers, req, &ciphers) 95 if err != nil { 96 return nil, err 97 } 98 return ciphers, nil 99 } 100 101 // Delete will delete the organization and the ciphers inside it. 102 func (o *Organization) Delete(inst *instance.Instance) error { 103 ciphers, err := o.FindCiphers(inst) 104 if err != nil { 105 return err 106 } 107 docs := make([]couchdb.Doc, len(ciphers)) 108 for i := range ciphers { 109 docs[i] = ciphers[i].Clone() 110 } 111 if err := couchdb.BulkDeleteDocs(inst, consts.BitwardenCiphers, docs); err != nil { 112 return err 113 } 114 115 return couchdb.DeleteDoc(inst, o) 116 } 117 118 // GetCozyOrganization returns the organization used to store the credentials 119 // for the konnectors running on the Cozy server. 120 func GetCozyOrganization(inst *instance.Instance, setting *settings.Settings) (*Organization, error) { 121 if setting == nil || setting.PublicKey == "" { 122 return nil, errors.New("No public key") 123 } 124 orgKey, err := setting.OrganizationKey() 125 if err != nil { 126 inst.Logger().WithNamespace("bitwarden"). 127 Infof("Cannot read the organization key: %s", err) 128 return nil, err 129 } 130 key, err := crypto.EncryptWithRSA(setting.PublicKey, orgKey) 131 if err != nil { 132 inst.Logger().WithNamespace("bitwarden"). 133 Infof("Cannot encrypt with RSA: %s", err) 134 return nil, err 135 } 136 137 iv := crypto.GenerateRandomBytes(16) 138 payload := []byte(consts.BitwardenCozyCollectionName) 139 name, err := crypto.EncryptWithAES256HMAC(orgKey[:32], orgKey[32:], payload, iv) 140 if err != nil { 141 inst.Logger().WithNamespace("bitwarden"). 142 Infof("Cannot encrypt with AES: %s", err) 143 return nil, err 144 } 145 146 settings, err := inst.SettingsDocument() 147 if err != nil { 148 return nil, err 149 } 150 email, _ := settings.M["email"].(string) 151 publicName, _ := settings.M["public_name"].(string) 152 org := Organization{ 153 CouchID: setting.OrganizationID, 154 Name: consts.BitwardenCozyOrganizationName, 155 Members: map[string]OrgMember{ 156 inst.Domain: { 157 UserID: inst.ID(), 158 Email: email, 159 Name: publicName, 160 OrgKey: key, 161 Status: OrgMemberConfirmed, 162 Owner: true, 163 }, 164 }, 165 Collection: Collection{ 166 DocID: setting.CollectionID, 167 Name: name, 168 }, 169 } 170 if setting.Metadata != nil { 171 org.Metadata = *setting.Metadata 172 } 173 return &org, nil 174 } 175 176 // FindAllOrganizations returns all the organizations, including the Cozy one. 177 func FindAllOrganizations(inst *instance.Instance, setting *settings.Settings) ([]*Organization, error) { 178 var orgs []*Organization 179 req := &couchdb.AllDocsRequest{} 180 if err := couchdb.GetAllDocs(inst, consts.BitwardenOrganizations, req, &orgs); err != nil { 181 if couchdb.IsNoDatabaseError(err) { 182 _ = couchdb.CreateDB(inst, consts.BitwardenOrganizations) 183 } else { 184 return nil, err 185 } 186 } 187 188 cozy, err := GetCozyOrganization(inst, setting) 189 if err != nil { 190 return nil, err 191 } 192 orgs = append(orgs, cozy) 193 return orgs, nil 194 } 195 196 var _ couchdb.Doc = &Organization{}