|
| 1 | +package dvls |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/json" |
| 5 | + "fmt" |
| 6 | + "net/http" |
| 7 | + "net/url" |
| 8 | +) |
| 9 | + |
| 10 | +type EntryWebsiteService service |
| 11 | + |
| 12 | +// EntryWebsite represents a website entry in DVLS |
| 13 | +type EntryWebsite struct { |
| 14 | + ID string `json:"id,omitempty"` |
| 15 | + VaultId string `json:"repositoryId"` |
| 16 | + EntryName string `json:"name"` |
| 17 | + Description string `json:"description"` |
| 18 | + EntryFolderPath string `json:"group"` |
| 19 | + ModifiedDate *ServerTime `json:"modifiedDate,omitempty"` |
| 20 | + ConnectionType ServerConnectionType `json:"connectionType"` |
| 21 | + ConnectionSubType ServerConnectionSubType `json:"connectionSubType"` |
| 22 | + Tags []string `json:"keywords,omitempty"` |
| 23 | + |
| 24 | + WebsiteDetails EntryWebsiteAuthDetails `json:"data"` |
| 25 | +} |
| 26 | + |
| 27 | +// MarshalJSON implements the json.Marshaler interface. |
| 28 | +func (e EntryWebsite) MarshalJSON() ([]byte, error) { |
| 29 | + raw := struct { |
| 30 | + ID string `json:"id,omitempty"` |
| 31 | + RepositoryId string `json:"repositoryId"` |
| 32 | + Name string `json:"name"` |
| 33 | + Description string `json:"description"` |
| 34 | + Events struct { |
| 35 | + OpenCommentPrompt bool `json:"openCommentPrompt"` |
| 36 | + CredentialViewedPrompt bool `json:"credentialViewedPrompt"` |
| 37 | + TicketNumberIsRequiredOnCredentialViewed bool `json:"ticketNumberIsRequiredOnCredentialViewed"` |
| 38 | + TicketNumberIsRequiredOnClose bool `json:"ticketNumberIsRequiredOnClose"` |
| 39 | + CredentialViewedCommentIsRequired bool `json:"credentialViewedCommentIsRequired"` |
| 40 | + TicketNumberIsRequiredOnOpen bool `json:"ticketNumberIsRequiredOnOpen"` |
| 41 | + CloseCommentIsRequired bool `json:"closeCommentIsRequired"` |
| 42 | + OpenCommentPromptOnBrowserExtensionLink bool `json:"openCommentPromptOnBrowserExtensionLink"` |
| 43 | + CloseCommentPrompt bool `json:"closeCommentPrompt"` |
| 44 | + OpenCommentIsRequired bool `json:"openCommentIsRequired"` |
| 45 | + WarnIfAlreadyOpened bool `json:"warnIfAlreadyOpened"` |
| 46 | + } `json:"events"` |
| 47 | + Data string `json:"data"` |
| 48 | + Expiration string `json:"expiration"` |
| 49 | + CheckOutMode int `json:"checkOutMode"` |
| 50 | + Group string `json:"group"` |
| 51 | + ConnectionType ServerConnectionType `json:"connectionType"` |
| 52 | + ConnectionSubType ServerConnectionSubType `json:"connectionSubType"` |
| 53 | + Keywords string `json:"keywords"` |
| 54 | + }{} |
| 55 | + |
| 56 | + raw.ID = e.ID |
| 57 | + raw.Keywords = sliceToKeywords(e.Tags) |
| 58 | + raw.Description = e.Description |
| 59 | + raw.RepositoryId = e.VaultId |
| 60 | + raw.Group = e.EntryFolderPath |
| 61 | + raw.ConnectionSubType = e.ConnectionSubType |
| 62 | + raw.ConnectionType = e.ConnectionType |
| 63 | + raw.Name = e.EntryName |
| 64 | + sensitiveJson, err := json.Marshal(e.WebsiteDetails) |
| 65 | + if err != nil { |
| 66 | + return nil, fmt.Errorf("failed to marshal sensitive data. error: %w", err) |
| 67 | + } |
| 68 | + |
| 69 | + raw.Data = string(sensitiveJson) |
| 70 | + |
| 71 | + entryJson, err := json.Marshal(raw) |
| 72 | + if err != nil { |
| 73 | + return nil, err |
| 74 | + } |
| 75 | + |
| 76 | + return entryJson, nil |
| 77 | +} |
| 78 | + |
| 79 | +// UnmarshalJSON implements the json.Unmarshaler interface. |
| 80 | +func (e *EntryWebsite) UnmarshalJSON(d []byte) error { |
| 81 | + raw := struct { |
| 82 | + ID string `json:"id"` |
| 83 | + Description string `json:"description"` |
| 84 | + Name string `json:"name"` |
| 85 | + Group string `json:"group"` |
| 86 | + ModifiedDate *ServerTime `json:"modifiedDate"` |
| 87 | + Keywords string `json:"keywords"` |
| 88 | + RepositoryId string `json:"repositoryId"` |
| 89 | + ConnectionType ServerConnectionType `json:"connectionType"` |
| 90 | + ConnectionSubType ServerConnectionSubType `json:"connectionSubType"` |
| 91 | + Data json.RawMessage `json:"data"` |
| 92 | + }{} |
| 93 | + |
| 94 | + err := json.Unmarshal(d, &raw) |
| 95 | + if err != nil { |
| 96 | + return err |
| 97 | + } |
| 98 | + |
| 99 | + e.ID = raw.ID |
| 100 | + e.EntryName = raw.Name |
| 101 | + e.ConnectionType = raw.ConnectionType |
| 102 | + e.ConnectionSubType = raw.ConnectionSubType |
| 103 | + e.ModifiedDate = raw.ModifiedDate |
| 104 | + e.Description = raw.Description |
| 105 | + e.EntryFolderPath = raw.Group |
| 106 | + e.VaultId = raw.RepositoryId |
| 107 | + e.Tags = keywordsToSlice(raw.Keywords) |
| 108 | + |
| 109 | + if len(raw.Data) > 0 { |
| 110 | + if err := json.Unmarshal(raw.Data, &e.WebsiteDetails); err != nil { |
| 111 | + return fmt.Errorf("failed to unmarshal website details: %w", err) |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + return nil |
| 116 | +} |
| 117 | + |
| 118 | +// EntryWebsiteAuthDetails represents website-specific fields |
| 119 | +type EntryWebsiteAuthDetails struct { |
| 120 | + Username string |
| 121 | + Password *string |
| 122 | + URL string |
| 123 | + WebBrowserApplication int |
| 124 | +} |
| 125 | + |
| 126 | +// MarshalJSON implements the json.Marshaler interface. |
| 127 | +func (s EntryWebsiteAuthDetails) MarshalJSON() ([]byte, error) { |
| 128 | + raw := struct { |
| 129 | + AutoFillLogin bool `json:"AutoFillLogin"` |
| 130 | + AutoSubmit bool `json:"AutoSubmit"` |
| 131 | + AutomaticRefreshTime int `json:"AutomaticRefreshTime"` |
| 132 | + ChromeProxyType int `json:"ChromeProxyType"` |
| 133 | + CustomJavaScript string `json:"CustomJavaScript"` |
| 134 | + Host string `json:"Host"` |
| 135 | + URL string `json:"URL"` |
| 136 | + Username string `json:"Username"` |
| 137 | + WebBrowserApplication int `json:"WebBrowserApplication"` |
| 138 | + PasswordItem struct { |
| 139 | + HasSensitiveData bool `json:"HasSensitiveData"` |
| 140 | + SensitiveData string `json:"SensitiveData"` |
| 141 | + } `json:"PasswordItem"` |
| 142 | + VPN struct { |
| 143 | + EnableAutoDetectIsOnlineVPN int `json:"EnableAutoDetectIsOnlineVPN"` |
| 144 | + } `json:"VPN"` |
| 145 | + }{} |
| 146 | + |
| 147 | + if s.Password != nil { |
| 148 | + raw.PasswordItem.HasSensitiveData = true |
| 149 | + raw.PasswordItem.SensitiveData = *s.Password |
| 150 | + } else { |
| 151 | + raw.PasswordItem.HasSensitiveData = false |
| 152 | + } |
| 153 | + |
| 154 | + raw.Username = s.Username |
| 155 | + raw.URL = s.URL |
| 156 | + raw.WebBrowserApplication = s.WebBrowserApplication |
| 157 | + |
| 158 | + secretJson, err := json.Marshal(raw) |
| 159 | + if err != nil { |
| 160 | + return nil, err |
| 161 | + } |
| 162 | + |
| 163 | + return secretJson, nil |
| 164 | +} |
| 165 | + |
| 166 | +// GetWebsiteDetails returns entry with the entry.WebsiteDetails.Password field. |
| 167 | +func (c *EntryWebsiteService) GetWebsiteDetails(entry EntryWebsite) (EntryWebsite, error) { |
| 168 | + var respData struct { |
| 169 | + Data string `json:"data"` |
| 170 | + } |
| 171 | + |
| 172 | + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, entry.ID, "/sensitive-data") |
| 173 | + if err != nil { |
| 174 | + return EntryWebsite{}, fmt.Errorf("failed to build entry url. error: %w", err) |
| 175 | + } |
| 176 | + |
| 177 | + resp, err := c.client.Request(reqUrl, http.MethodPost, nil) |
| 178 | + if err != nil { |
| 179 | + return EntryWebsite{}, fmt.Errorf("error while fetching sensitive data. error: %w", err) |
| 180 | + } else if err = resp.CheckRespSaveResult(); err != nil { |
| 181 | + return EntryWebsite{}, err |
| 182 | + } |
| 183 | + |
| 184 | + if err := json.Unmarshal(resp.Response, &respData); err != nil { |
| 185 | + return EntryWebsite{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) |
| 186 | + } |
| 187 | + |
| 188 | + var sensitiveDataResponse struct { |
| 189 | + Data struct { |
| 190 | + PasswordItem struct { |
| 191 | + HasSensitiveData bool `json:"hasSensitiveData"` |
| 192 | + SensitiveData *string `json:"sensitiveData,omitempty"` |
| 193 | + } `json:"passwordItem"` |
| 194 | + } `json:"data"` |
| 195 | + } |
| 196 | + |
| 197 | + if err := json.Unmarshal([]byte(respData.Data), &sensitiveDataResponse); err != nil { |
| 198 | + return EntryWebsite{}, fmt.Errorf("failed to unmarshal inner data. error: %w", err) |
| 199 | + } |
| 200 | + |
| 201 | + if sensitiveDataResponse.Data.PasswordItem.HasSensitiveData { |
| 202 | + entry.WebsiteDetails.Password = sensitiveDataResponse.Data.PasswordItem.SensitiveData |
| 203 | + } else { |
| 204 | + entry.WebsiteDetails.Password = nil |
| 205 | + } |
| 206 | + |
| 207 | + return entry, nil |
| 208 | +} |
| 209 | + |
| 210 | +// Get returns a single Entry specified by entryId. Call GetWebsiteDetails with |
| 211 | +// the returned Entry to fetch the password. |
| 212 | +func (s *EntryWebsiteService) Get(entryId string) (EntryWebsite, error) { |
| 213 | + var respData struct { |
| 214 | + Data EntryWebsite `json:"data"` |
| 215 | + } |
| 216 | + |
| 217 | + reqUrl, err := url.JoinPath(s.client.baseUri, entryEndpoint, entryId) |
| 218 | + if err != nil { |
| 219 | + return EntryWebsite{}, fmt.Errorf("failed to build entry url: %w", err) |
| 220 | + } |
| 221 | + |
| 222 | + resp, err := s.client.Request(reqUrl, http.MethodGet, nil) |
| 223 | + if err != nil { |
| 224 | + return EntryWebsite{}, fmt.Errorf("error fetching entry: %w", err) |
| 225 | + } |
| 226 | + if err = resp.CheckRespSaveResult(); err != nil { |
| 227 | + return EntryWebsite{}, err |
| 228 | + } |
| 229 | + if resp.Response == nil { |
| 230 | + return EntryWebsite{}, fmt.Errorf("response body is nil for request to %s", reqUrl) |
| 231 | + } |
| 232 | + |
| 233 | + if err := json.Unmarshal(resp.Response, &respData); err != nil { |
| 234 | + return EntryWebsite{}, fmt.Errorf("failed to unmarshal response: %w", err) |
| 235 | + } |
| 236 | + |
| 237 | + return respData.Data, nil |
| 238 | +} |
| 239 | + |
| 240 | +// NewEntryWebsiteAuthDetails returns an EntryWebsiteAuthDetails with an initialised EntryWebsiteAuthDetails.Password |
| 241 | +func (c *EntryWebsiteService) NewWebsiteDetails(username, password string) EntryWebsiteAuthDetails { |
| 242 | + return EntryWebsiteAuthDetails{ |
| 243 | + Username: username, |
| 244 | + Password: &password, |
| 245 | + } |
| 246 | +} |
0 commit comments