onepassword_test.go 74 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package onepassword
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "reflect"
  20. "testing"
  21. "github.com/1Password/connect-sdk-go/onepassword"
  22. corev1 "k8s.io/api/core/v1"
  23. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  27. "github.com/external-secrets/external-secrets/providers/v1/onepassword/fake"
  28. "github.com/external-secrets/external-secrets/runtime/esutils/metadata"
  29. )
  30. const (
  31. // vaults and items.
  32. myVault, myVaultID = "my-vault", "my-vault-id"
  33. myItem, myItemID = "my-item", "my-item-id"
  34. myNativeItemID = "gdpvdudxrico74msloimk7qjna"
  35. mySharedVault, mySharedVaultID = "my-shared-vault", "my-shared-vault-id"
  36. mySharedItem, mySharedItemID = "my-shared-item", "my-shared-item-id"
  37. myOtherVault, myOtherVaultID = "my-other-vault", "my-other-vault-id"
  38. myOtherItem, myOtherItemID = "my-other-item", "my-other-item-id"
  39. myNonMatchingVault, myNonMatchingVaultID = "my-non-matching-vault", "my-non-matching-vault-id"
  40. myNonMatchingItem, myNonMatchingItemID = "my-non-matching-item", "my-non-matching-item-id"
  41. // fields and files.
  42. key1, key2, key3, key4 = "key1", "key2", "key3", "key4"
  43. value1, value2, value3, value4 = "value1", "value2", "value3", "value4"
  44. sharedKey1, sharedValue1 = "sharedkey1", "sharedvalue1"
  45. otherKey1 = "otherkey1"
  46. filePNG, filePNGID = "file.png", "file-id"
  47. myFilePNG, myFilePNGID, myContents = "my-file.png", "my-file-id", "my-contents"
  48. mySecondFileTXT, mySecondFileTXTID = "my-second-file.txt", "my-second-file-id"
  49. mySecondContents = "my-second-contents"
  50. myFile2PNG, myFile2TXT = "my-file-2.png", "my-file-2.txt"
  51. myFile2ID, myContents2 = "my-file-2-id", "my-contents-2"
  52. myOtherFilePNG, myOtherFilePNGID = "my-other-file.png", "my-other-file-id"
  53. myOtherContents = "my-other-contents"
  54. nonMatchingFilePNG, nonMatchingFilePNGID = "non-matching-file.png", "non-matching-file-id"
  55. nonMatchingContents = "non-matching-contents"
  56. // other.
  57. mySecret, token, password = "my-secret", "token", "password"
  58. one, two, three = "one", "two", "three"
  59. connectHost = "https://example.com"
  60. setupCheckFormat = "Setup: '%s', Check: '%s'"
  61. getSecretMapErrFormat = "%s: onepassword.GetSecretMap(...): -expected, +got:\n-%#v\n+%#v\n"
  62. getSecretErrFormat = "%s: onepassword.GetSecret(...): -expected, +got:\n-%#v\n+%#v\n"
  63. getAllSecretsErrFormat = "%s: onepassword.GetAllSecrets(...): -expected, +got:\n-%#v\n+%#v\n"
  64. validateStoreErrFormat = "%s: onepassword.validateStore(...): -expected, +got:\n-%#v\n+%#v\n"
  65. findItemErrFormat = "%s: onepassword.findItem(...): -expected, +got:\n-%#v\n+%#v\n"
  66. errFromErrMsgF = "%w: %s"
  67. errDoesNotMatchMsgF = "%s: error did not match: -expected, +got:\\n-%#v\\n+%#v\\n"
  68. )
  69. func TestFindItem(t *testing.T) {
  70. type check struct {
  71. checkNote string
  72. findItemName string
  73. expectedItem *onepassword.Item
  74. expectedErr error
  75. }
  76. type testCase struct {
  77. setupNote string
  78. provider *ProviderOnePassword
  79. checks []check
  80. }
  81. testCases := []testCase{
  82. {
  83. setupNote: "valid basic: one vault, one item, one field",
  84. provider: &ProviderOnePassword{
  85. vaults: map[string]int{myVault: 1},
  86. client: fake.NewMockClient().
  87. AddPredictableVault(myVault).
  88. AddPredictableItemWithField(myVault, myItem, key1, value1),
  89. },
  90. checks: []check{
  91. {
  92. checkNote: "pass",
  93. findItemName: myItem,
  94. expectedErr: nil,
  95. expectedItem: &onepassword.Item{
  96. ID: myItemID,
  97. Title: myItem,
  98. Vault: onepassword.ItemVault{ID: myVaultID},
  99. Fields: []*onepassword.ItemField{
  100. {
  101. Label: key1,
  102. Value: value1,
  103. },
  104. },
  105. },
  106. },
  107. },
  108. },
  109. {
  110. setupNote: "native item ID: one vault, one item, one field",
  111. provider: &ProviderOnePassword{
  112. vaults: map[string]int{myVault: 1},
  113. client: fake.NewMockClient().
  114. AddPredictableVault(myVault).
  115. AppendItem(myVaultID, onepassword.Item{
  116. ID: myNativeItemID,
  117. Title: "My App (Production)",
  118. Vault: onepassword.ItemVault{ID: myVaultID},
  119. }).
  120. AppendItemField(myVaultID, myNativeItemID, onepassword.ItemField{
  121. Label: key1,
  122. Value: value1,
  123. }),
  124. },
  125. checks: []check{
  126. {
  127. checkNote: "find by native item ID",
  128. findItemName: myNativeItemID,
  129. expectedErr: nil,
  130. expectedItem: &onepassword.Item{
  131. ID: myNativeItemID,
  132. Title: "My App (Production)",
  133. Vault: onepassword.ItemVault{ID: myVaultID},
  134. Fields: []*onepassword.ItemField{
  135. {
  136. Label: key1,
  137. Value: value1,
  138. },
  139. },
  140. },
  141. },
  142. },
  143. },
  144. {
  145. setupNote: "multiple vaults, multiple items",
  146. provider: &ProviderOnePassword{
  147. vaults: map[string]int{myVault: 1, mySharedVault: 2},
  148. client: fake.NewMockClient().
  149. AddPredictableVault(myVault).
  150. AddPredictableItemWithField(myVault, myItem, key1, value1).
  151. AddPredictableVault(mySharedVault).
  152. AddPredictableItemWithField(mySharedVault, mySharedItem, sharedKey1, sharedValue1),
  153. },
  154. checks: []check{
  155. {
  156. checkNote: "can still get myItem",
  157. findItemName: myItem,
  158. expectedErr: nil,
  159. expectedItem: &onepassword.Item{
  160. ID: myItemID,
  161. Title: myItem,
  162. Vault: onepassword.ItemVault{ID: myVaultID},
  163. Fields: []*onepassword.ItemField{
  164. {
  165. Label: key1,
  166. Value: value1,
  167. },
  168. },
  169. },
  170. },
  171. {
  172. checkNote: "can also get mySharedItem",
  173. findItemName: mySharedItem,
  174. expectedErr: nil,
  175. expectedItem: &onepassword.Item{
  176. ID: mySharedItemID,
  177. Title: mySharedItem,
  178. Vault: onepassword.ItemVault{ID: mySharedVaultID},
  179. Fields: []*onepassword.ItemField{
  180. {
  181. Label: sharedKey1,
  182. Value: sharedValue1,
  183. },
  184. },
  185. },
  186. },
  187. },
  188. },
  189. {
  190. setupNote: "multiple vault matches when should be one",
  191. provider: &ProviderOnePassword{
  192. vaults: map[string]int{myVault: 1, mySharedVault: 2},
  193. client: fake.NewMockClient().
  194. AppendVault(myVault, onepassword.Vault{
  195. ID: myVaultID,
  196. Name: myVault,
  197. }).
  198. AppendVault(myVault, onepassword.Vault{
  199. ID: "my-vault-extra-match-id",
  200. Name: "my-vault-extra-match",
  201. }),
  202. },
  203. checks: []check{
  204. {
  205. checkNote: "two vaults",
  206. findItemName: myItem,
  207. expectedErr: errors.New("key not found in 1Password Vaults: my-item in: map[my-shared-vault:2 my-vault:1]"),
  208. },
  209. },
  210. },
  211. {
  212. setupNote: "no item matches when should be one",
  213. provider: &ProviderOnePassword{
  214. vaults: map[string]int{myVault: 1},
  215. client: fake.NewMockClient().
  216. AddPredictableVault(myVault),
  217. },
  218. checks: []check{
  219. {
  220. checkNote: "no exist",
  221. findItemName: "my-item-no-exist",
  222. expectedErr: fmt.Errorf("%w: my-item-no-exist in: map[my-vault:1]", ErrKeyNotFound),
  223. },
  224. },
  225. },
  226. {
  227. setupNote: "multiple item matches when should be one",
  228. provider: &ProviderOnePassword{
  229. vaults: map[string]int{myVault: 1},
  230. client: fake.NewMockClient().
  231. AddPredictableVault(myVault).
  232. AddPredictableItemWithField(myVault, myItem, key1, value1).
  233. AppendItem(myVaultID, onepassword.Item{
  234. ID: "asdf",
  235. Title: myItem,
  236. Vault: onepassword.ItemVault{ID: myVaultID},
  237. }),
  238. },
  239. checks: []check{
  240. {
  241. checkNote: "multiple match",
  242. findItemName: myItem,
  243. expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneItem, "'my-item', got 2"),
  244. },
  245. },
  246. },
  247. {
  248. setupNote: "ordered vaults",
  249. provider: &ProviderOnePassword{
  250. vaults: map[string]int{myVault: 1, mySharedVault: 2, myOtherVault: 3},
  251. client: fake.NewMockClient().
  252. AddPredictableVault(myVault).
  253. AddPredictableVault(mySharedVault).
  254. AddPredictableVault(myOtherVault).
  255. // // my-item
  256. // returned: my-item in my-vault
  257. AddPredictableItemWithField(myVault, myItem, key1, value1).
  258. // preempted: my-item in my-shared-vault
  259. AppendItem(mySharedVaultID, onepassword.Item{
  260. ID: myItemID,
  261. Title: myItem,
  262. Vault: onepassword.ItemVault{ID: mySharedVaultID},
  263. }).
  264. AppendItemField(mySharedVaultID, myItemID, onepassword.ItemField{
  265. Label: key1,
  266. Value: "value1-from-my-shared-vault",
  267. }).
  268. // preempted: my-item in my-other-vault
  269. AppendItem(myOtherVaultID, onepassword.Item{
  270. ID: myItemID,
  271. Title: myItem,
  272. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  273. }).
  274. AppendItemField(myOtherVaultID, myItemID, onepassword.ItemField{
  275. Label: key1,
  276. Value: "value1-from-my-other-vault",
  277. }).
  278. // // my-shared-item
  279. // returned: my-shared-item in my-shared-vault
  280. AddPredictableItemWithField(mySharedVault, mySharedItem, sharedKey1, "sharedvalue1-from-my-shared-vault").
  281. // preempted: my-shared-item in my-other-vault
  282. AppendItem(myOtherVaultID, onepassword.Item{
  283. ID: mySharedItemID,
  284. Title: mySharedItem,
  285. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  286. }).
  287. AppendItemField(myOtherVaultID, mySharedItemID, onepassword.ItemField{
  288. Label: sharedKey1,
  289. Value: "sharedvalue1-from-my-other-vault",
  290. }).
  291. // // my-other-item
  292. // returned: my-other-item in my-other-vault
  293. AddPredictableItemWithField(myOtherVault, myOtherItem, otherKey1, "othervalue1-from-my-other-vault"),
  294. },
  295. checks: []check{
  296. {
  297. // my-item in all three vaults, gets the one from my-vault
  298. checkNote: "gets item from my-vault",
  299. findItemName: myItem,
  300. expectedErr: nil,
  301. expectedItem: &onepassword.Item{
  302. ID: myItemID,
  303. Title: myItem,
  304. Vault: onepassword.ItemVault{ID: myVaultID},
  305. Fields: []*onepassword.ItemField{
  306. {
  307. Label: key1,
  308. Value: value1,
  309. },
  310. },
  311. },
  312. },
  313. {
  314. // my-shared-item in my-shared-vault and my-other-vault, gets the one from my-shared-vault
  315. checkNote: "gets item from my-shared-vault",
  316. findItemName: mySharedItem,
  317. expectedErr: nil,
  318. expectedItem: &onepassword.Item{
  319. ID: mySharedItemID,
  320. Title: mySharedItem,
  321. Vault: onepassword.ItemVault{ID: mySharedVaultID},
  322. Fields: []*onepassword.ItemField{
  323. {
  324. Label: sharedKey1,
  325. Value: "sharedvalue1-from-my-shared-vault",
  326. },
  327. },
  328. },
  329. },
  330. {
  331. // my-other-item in my-other-vault
  332. checkNote: "gets item from my-other-vault",
  333. findItemName: myOtherItem,
  334. expectedErr: nil,
  335. expectedItem: &onepassword.Item{
  336. ID: myOtherItemID,
  337. Title: myOtherItem,
  338. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  339. Fields: []*onepassword.ItemField{
  340. {
  341. Label: otherKey1,
  342. Value: "othervalue1-from-my-other-vault",
  343. },
  344. },
  345. },
  346. },
  347. },
  348. },
  349. }
  350. // run the tests
  351. for num, tc := range testCases {
  352. t.Run(fmt.Sprintf("test-%d", num), func(t *testing.T) {
  353. for _, check := range tc.checks {
  354. got, err := tc.provider.findItem(check.findItemName)
  355. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  356. if check.expectedErr == nil && err != nil {
  357. // expected no error, got one
  358. t.Errorf(findItemErrFormat, notes, nil, err)
  359. }
  360. if check.expectedErr != nil && err == nil {
  361. // expected an error, didn't get one
  362. t.Errorf(findItemErrFormat, notes, check.expectedErr.Error(), nil)
  363. }
  364. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  365. // expected an error, got the wrong one
  366. t.Errorf(findItemErrFormat, notes, check.expectedErr.Error(), err.Error())
  367. }
  368. if check.expectedItem != nil {
  369. if !reflect.DeepEqual(check.expectedItem, got) {
  370. // expected a predefined item, got something else
  371. t.Errorf(findItemErrFormat, notes, check.expectedItem, got)
  372. }
  373. }
  374. }
  375. })
  376. }
  377. }
  378. func TestProviderOnePasswordSecretExists(t *testing.T) {
  379. connectErr := errors.New("1Password Connect unavailable")
  380. testCases := []struct {
  381. name string
  382. provider *ProviderOnePassword
  383. ref fakeRef
  384. expected bool
  385. expectedErr error
  386. }{
  387. {
  388. name: "item does not exist",
  389. provider: &ProviderOnePassword{
  390. vaults: map[string]int{myVault: 1},
  391. client: fake.NewMockClient().
  392. AddPredictableVault(myVault),
  393. },
  394. ref: fakeRef{key: myItem},
  395. },
  396. {
  397. name: "item exists without requested property",
  398. provider: &ProviderOnePassword{
  399. vaults: map[string]int{myVault: 1},
  400. client: fake.NewMockClient().
  401. AddPredictableVault(myVault).
  402. AddPredictableItemWithField(myVault, myItem, key1, value1),
  403. },
  404. ref: fakeRef{key: myItem},
  405. expected: true,
  406. },
  407. {
  408. name: "requested field exists",
  409. provider: &ProviderOnePassword{
  410. vaults: map[string]int{myVault: 1},
  411. client: fake.NewMockClient().
  412. AddPredictableVault(myVault).
  413. AddPredictableItemWithField(myVault, myItem, key1, value1),
  414. },
  415. ref: fakeRef{key: myItem, prop: key1},
  416. expected: true,
  417. },
  418. {
  419. name: "requested field does not exist",
  420. provider: &ProviderOnePassword{
  421. vaults: map[string]int{myVault: 1},
  422. client: fake.NewMockClient().
  423. AddPredictableVault(myVault).
  424. AddPredictableItemWithField(myVault, myItem, key1, value1),
  425. },
  426. ref: fakeRef{key: myItem, prop: key2},
  427. },
  428. {
  429. name: "duplicate items return an error",
  430. provider: &ProviderOnePassword{
  431. vaults: map[string]int{myVault: 1},
  432. client: fake.NewMockClient().
  433. AddPredictableVault(myVault).
  434. AddPredictableItemWithField(myVault, myItem, key1, value1).
  435. AppendItem(myVaultID, onepassword.Item{
  436. ID: "duplicate-item-id",
  437. Title: myItem,
  438. Vault: onepassword.ItemVault{ID: myVaultID},
  439. }),
  440. },
  441. ref: fakeRef{key: myItem},
  442. expectedErr: ErrExpectedOneItem,
  443. },
  444. {
  445. name: "Connect lookup errors are propagated",
  446. provider: &ProviderOnePassword{
  447. vaults: map[string]int{myVault: 1},
  448. client: &mockClient{
  449. getVaultFunc: func(string) (*onepassword.Vault, error) {
  450. return nil, connectErr
  451. },
  452. },
  453. },
  454. ref: fakeRef{key: myItem},
  455. expectedErr: connectErr,
  456. },
  457. }
  458. for _, tc := range testCases {
  459. t.Run(tc.name, func(t *testing.T) {
  460. exists, err := tc.provider.SecretExists(context.Background(), tc.ref)
  461. if exists != tc.expected {
  462. t.Errorf("expected exists=%v, got %v", tc.expected, exists)
  463. }
  464. if !errors.Is(err, tc.expectedErr) {
  465. t.Errorf("expected error %v, got %v", tc.expectedErr, err)
  466. }
  467. })
  468. }
  469. }
  470. func TestValidateStore(t *testing.T) {
  471. type testCase struct {
  472. checkNote string
  473. store *esv1.SecretStore
  474. clusterStore *esv1.ClusterSecretStore
  475. expectedErr error
  476. }
  477. testCases := []testCase{
  478. {
  479. checkNote: "invalid: nil provider",
  480. store: &esv1.SecretStore{
  481. TypeMeta: metav1.TypeMeta{
  482. Kind: "SecretStore",
  483. },
  484. Spec: esv1.SecretStoreSpec{
  485. Provider: nil,
  486. },
  487. },
  488. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreNilSpecProvider)),
  489. },
  490. {
  491. checkNote: "invalid: nil OnePassword provider spec",
  492. store: &esv1.SecretStore{
  493. TypeMeta: metav1.TypeMeta{
  494. Kind: "SecretStore",
  495. },
  496. Spec: esv1.SecretStoreSpec{
  497. Provider: &esv1.SecretStoreProvider{
  498. OnePassword: nil,
  499. },
  500. },
  501. },
  502. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreNilSpecProviderOnePassword)),
  503. },
  504. {
  505. checkNote: "valid secretStore",
  506. store: &esv1.SecretStore{
  507. TypeMeta: metav1.TypeMeta{
  508. Kind: "SecretStore",
  509. },
  510. Spec: esv1.SecretStoreSpec{
  511. Provider: &esv1.SecretStoreProvider{
  512. OnePassword: &esv1.OnePasswordProvider{
  513. Auth: &esv1.OnePasswordAuth{
  514. SecretRef: &esv1.OnePasswordAuthSecretRef{
  515. ConnectToken: esmeta.SecretKeySelector{
  516. Name: mySecret,
  517. Key: token,
  518. },
  519. },
  520. },
  521. ConnectHost: connectHost,
  522. Vaults: map[string]int{
  523. myVault: 1,
  524. },
  525. },
  526. },
  527. },
  528. },
  529. expectedErr: nil,
  530. },
  531. {
  532. checkNote: "invalid: illegal namespace on SecretStore",
  533. store: &esv1.SecretStore{
  534. TypeMeta: metav1.TypeMeta{
  535. Kind: "SecretStore",
  536. },
  537. Spec: esv1.SecretStoreSpec{
  538. Provider: &esv1.SecretStoreProvider{
  539. OnePassword: &esv1.OnePasswordProvider{
  540. Auth: &esv1.OnePasswordAuth{
  541. SecretRef: &esv1.OnePasswordAuthSecretRef{
  542. ConnectToken: esmeta.SecretKeySelector{
  543. Name: mySecret,
  544. Namespace: new("my-namespace"),
  545. Key: token,
  546. },
  547. },
  548. },
  549. ConnectHost: connectHost,
  550. Vaults: map[string]int{
  551. myVault: 1,
  552. myOtherVault: 2,
  553. },
  554. },
  555. },
  556. },
  557. },
  558. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New("namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore")),
  559. },
  560. {
  561. checkNote: "invalid: more than one vault with the same number",
  562. store: &esv1.SecretStore{
  563. TypeMeta: metav1.TypeMeta{
  564. Kind: "SecretStore",
  565. },
  566. Spec: esv1.SecretStoreSpec{
  567. Provider: &esv1.SecretStoreProvider{
  568. OnePassword: &esv1.OnePasswordProvider{
  569. Auth: &esv1.OnePasswordAuth{
  570. SecretRef: &esv1.OnePasswordAuthSecretRef{
  571. ConnectToken: esmeta.SecretKeySelector{
  572. Name: mySecret,
  573. Key: token,
  574. },
  575. },
  576. },
  577. ConnectHost: connectHost,
  578. Vaults: map[string]int{
  579. myVault: 1,
  580. myOtherVault: 1,
  581. },
  582. },
  583. },
  584. },
  585. },
  586. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreNonUniqueVaultNumbers)),
  587. },
  588. {
  589. checkNote: "valid: clusterSecretStore",
  590. clusterStore: &esv1.ClusterSecretStore{
  591. TypeMeta: metav1.TypeMeta{
  592. Kind: "ClusterSecretStore",
  593. },
  594. Spec: esv1.SecretStoreSpec{
  595. Provider: &esv1.SecretStoreProvider{
  596. OnePassword: &esv1.OnePasswordProvider{
  597. Auth: &esv1.OnePasswordAuth{
  598. SecretRef: &esv1.OnePasswordAuthSecretRef{
  599. ConnectToken: esmeta.SecretKeySelector{
  600. Name: mySecret,
  601. Namespace: new("my-namespace"),
  602. Key: token,
  603. },
  604. },
  605. },
  606. ConnectHost: connectHost,
  607. Vaults: map[string]int{
  608. myVault: 1,
  609. },
  610. },
  611. },
  612. },
  613. },
  614. expectedErr: nil,
  615. },
  616. {
  617. checkNote: "invalid: clusterSecretStore without namespace",
  618. clusterStore: &esv1.ClusterSecretStore{
  619. TypeMeta: metav1.TypeMeta{
  620. Kind: "ClusterSecretStore",
  621. },
  622. Spec: esv1.SecretStoreSpec{
  623. Provider: &esv1.SecretStoreProvider{
  624. OnePassword: &esv1.OnePasswordProvider{
  625. Auth: &esv1.OnePasswordAuth{
  626. SecretRef: &esv1.OnePasswordAuthSecretRef{
  627. ConnectToken: esmeta.SecretKeySelector{
  628. Name: mySecret,
  629. Key: token,
  630. },
  631. },
  632. },
  633. ConnectHost: connectHost,
  634. Vaults: map[string]int{
  635. myVault: 1,
  636. myOtherVault: 2,
  637. },
  638. },
  639. },
  640. },
  641. },
  642. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New("cluster scope requires namespace")),
  643. },
  644. {
  645. checkNote: "invalid: missing connectTokenSecretRef.name",
  646. store: &esv1.SecretStore{
  647. TypeMeta: metav1.TypeMeta{
  648. Kind: "SecretStore",
  649. },
  650. Spec: esv1.SecretStoreSpec{
  651. Provider: &esv1.SecretStoreProvider{
  652. OnePassword: &esv1.OnePasswordProvider{
  653. Auth: &esv1.OnePasswordAuth{
  654. SecretRef: &esv1.OnePasswordAuthSecretRef{
  655. ConnectToken: esmeta.SecretKeySelector{
  656. Key: token,
  657. },
  658. },
  659. },
  660. ConnectHost: connectHost,
  661. Vaults: map[string]int{
  662. myVault: 1,
  663. myOtherVault: 2,
  664. },
  665. },
  666. },
  667. },
  668. },
  669. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreMissingRefName)),
  670. },
  671. {
  672. checkNote: "invalid: missing connectTokenSecretRef.key",
  673. store: &esv1.SecretStore{
  674. TypeMeta: metav1.TypeMeta{
  675. Kind: "SecretStore",
  676. },
  677. Spec: esv1.SecretStoreSpec{
  678. Provider: &esv1.SecretStoreProvider{
  679. OnePassword: &esv1.OnePasswordProvider{
  680. Auth: &esv1.OnePasswordAuth{
  681. SecretRef: &esv1.OnePasswordAuthSecretRef{
  682. ConnectToken: esmeta.SecretKeySelector{
  683. Name: mySecret,
  684. },
  685. },
  686. },
  687. ConnectHost: connectHost,
  688. Vaults: map[string]int{
  689. myVault: 1,
  690. myOtherVault: 2,
  691. },
  692. },
  693. },
  694. },
  695. },
  696. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreMissingRefKey)),
  697. },
  698. {
  699. checkNote: "invalid: at least one vault",
  700. store: &esv1.SecretStore{
  701. TypeMeta: metav1.TypeMeta{
  702. Kind: "SecretStore",
  703. },
  704. Spec: esv1.SecretStoreSpec{
  705. Provider: &esv1.SecretStoreProvider{
  706. OnePassword: &esv1.OnePasswordProvider{
  707. Auth: &esv1.OnePasswordAuth{
  708. SecretRef: &esv1.OnePasswordAuthSecretRef{
  709. ConnectToken: esmeta.SecretKeySelector{
  710. Name: mySecret,
  711. Key: token,
  712. },
  713. },
  714. },
  715. ConnectHost: connectHost,
  716. Vaults: map[string]int{},
  717. },
  718. },
  719. },
  720. },
  721. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreAtLeastOneVault)),
  722. },
  723. {
  724. checkNote: "invalid: url",
  725. store: &esv1.SecretStore{
  726. TypeMeta: metav1.TypeMeta{
  727. Kind: "SecretStore",
  728. },
  729. Spec: esv1.SecretStoreSpec{
  730. Provider: &esv1.SecretStoreProvider{
  731. OnePassword: &esv1.OnePasswordProvider{
  732. Auth: &esv1.OnePasswordAuth{
  733. SecretRef: &esv1.OnePasswordAuthSecretRef{
  734. ConnectToken: esmeta.SecretKeySelector{
  735. Name: mySecret,
  736. Key: token,
  737. },
  738. },
  739. },
  740. ConnectHost: ":/invalid.invalid",
  741. Vaults: map[string]int{
  742. myVault: 1,
  743. },
  744. },
  745. },
  746. },
  747. },
  748. expectedErr: fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreInvalidConnectHost, errors.New("parse \":/invalid.invalid\": missing protocol scheme"))),
  749. },
  750. }
  751. // run the tests
  752. for _, tc := range testCases {
  753. var err error
  754. if tc.store == nil {
  755. err = validateStore(tc.clusterStore)
  756. } else {
  757. err = validateStore(tc.store)
  758. }
  759. notes := fmt.Sprintf("Check: '%s'", tc.checkNote)
  760. if tc.expectedErr == nil && err != nil {
  761. // expected no error, got one
  762. t.Errorf(validateStoreErrFormat, notes, nil, err)
  763. }
  764. if tc.expectedErr != nil && err == nil {
  765. // expected an error, didn't get one
  766. t.Errorf(validateStoreErrFormat, notes, tc.expectedErr.Error(), nil)
  767. }
  768. if tc.expectedErr != nil && err != nil && err.Error() != tc.expectedErr.Error() {
  769. // expected an error, got the wrong one
  770. t.Errorf(validateStoreErrFormat, notes, tc.expectedErr.Error(), err.Error())
  771. }
  772. }
  773. }
  774. // most functionality is tested in TestFindItem
  775. //
  776. // here we just check that an empty Property defaults to "password",
  777. // files are loaded, and
  778. // the data or errors are properly returned
  779. func TestGetSecret(t *testing.T) {
  780. type check struct {
  781. checkNote string
  782. ref esv1.ExternalSecretDataRemoteRef
  783. expectedValue string
  784. expectedErr error
  785. }
  786. type testCase struct {
  787. setupNote string
  788. provider *ProviderOnePassword
  789. checks []check
  790. }
  791. testCases := []testCase{
  792. {
  793. setupNote: "one vault, one item, two fields",
  794. provider: &ProviderOnePassword{
  795. vaults: map[string]int{myVault: 1},
  796. client: fake.NewMockClient().
  797. AddPredictableVault(myVault).
  798. AppendItem(myVaultID, onepassword.Item{
  799. ID: myItemID,
  800. Title: myItem,
  801. Vault: onepassword.ItemVault{ID: myVaultID},
  802. Files: []*onepassword.File{
  803. {
  804. ID: myFilePNGID,
  805. Name: myFilePNG,
  806. },
  807. },
  808. }).
  809. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  810. Label: password,
  811. Value: value2,
  812. }).
  813. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  814. Label: key1,
  815. Value: value1,
  816. }).
  817. SetFileContents(myFilePNG, []byte(myContents)),
  818. },
  819. checks: []check{
  820. {
  821. checkNote: key1,
  822. ref: esv1.ExternalSecretDataRemoteRef{
  823. Key: myItem,
  824. Property: key1,
  825. },
  826. expectedValue: value1,
  827. expectedErr: nil,
  828. },
  829. {
  830. checkNote: key1 + " with prefix",
  831. ref: esv1.ExternalSecretDataRemoteRef{
  832. Key: myItem,
  833. Property: fieldPrefix + prefixSplitter + key1,
  834. },
  835. expectedValue: value1,
  836. expectedErr: nil,
  837. },
  838. {
  839. checkNote: "'password' (defaulted property)",
  840. ref: esv1.ExternalSecretDataRemoteRef{
  841. Key: myItem,
  842. },
  843. expectedValue: value2,
  844. expectedErr: nil,
  845. },
  846. {
  847. checkNote: "'ref.version' not implemented",
  848. ref: esv1.ExternalSecretDataRemoteRef{
  849. Key: myItem,
  850. Property: key1,
  851. Version: "123",
  852. },
  853. expectedErr: errors.New(errVersionNotImplemented),
  854. },
  855. {
  856. checkNote: "file named my-file.png with prefix",
  857. ref: esv1.ExternalSecretDataRemoteRef{
  858. Key: myItem,
  859. Property: filePrefix + prefixSplitter + myFilePNG,
  860. },
  861. expectedValue: myContents,
  862. expectedErr: nil,
  863. },
  864. },
  865. },
  866. {
  867. setupNote: "files are loaded",
  868. provider: &ProviderOnePassword{
  869. vaults: map[string]int{myVault: 1},
  870. client: fake.NewMockClient().
  871. AddPredictableVault(myVault).
  872. AppendItem(myVaultID, onepassword.Item{
  873. ID: myItemID,
  874. Title: myItem,
  875. Vault: onepassword.ItemVault{ID: myVaultID},
  876. Category: documentCategory,
  877. Files: []*onepassword.File{
  878. {
  879. ID: myFilePNGID,
  880. Name: myFilePNG,
  881. },
  882. },
  883. }).
  884. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  885. Label: key1,
  886. Value: value2,
  887. }).
  888. SetFileContents(myFilePNG, []byte(myContents)),
  889. },
  890. checks: []check{
  891. {
  892. checkNote: "field named password",
  893. ref: esv1.ExternalSecretDataRemoteRef{
  894. Key: myItem,
  895. Property: fieldPrefix + prefixSplitter + key1,
  896. },
  897. expectedValue: value2,
  898. expectedErr: nil,
  899. },
  900. {
  901. checkNote: "file named my-file.png",
  902. ref: esv1.ExternalSecretDataRemoteRef{
  903. Key: myItem,
  904. Property: myFilePNG,
  905. },
  906. expectedValue: myContents,
  907. expectedErr: nil,
  908. },
  909. {
  910. checkNote: "file named my-file.png with prefix",
  911. ref: esv1.ExternalSecretDataRemoteRef{
  912. Key: myItem,
  913. Property: filePrefix + prefixSplitter + myFilePNG,
  914. },
  915. expectedValue: myContents,
  916. expectedErr: nil,
  917. },
  918. {
  919. checkNote: "empty ref.Property",
  920. ref: esv1.ExternalSecretDataRemoteRef{
  921. Key: myItem,
  922. },
  923. expectedValue: myContents,
  924. expectedErr: nil,
  925. },
  926. {
  927. checkNote: "file non existent",
  928. ref: esv1.ExternalSecretDataRemoteRef{
  929. Key: myItem,
  930. Property: "you-cant-find-me.png",
  931. },
  932. expectedErr: fmt.Errorf(errDocumentNotFound, errors.New("'my-item', 'you-cant-find-me.png'")),
  933. },
  934. {
  935. checkNote: "file non existent with prefix",
  936. ref: esv1.ExternalSecretDataRemoteRef{
  937. Key: myItem,
  938. Property: "file/you-cant-find-me.png",
  939. },
  940. expectedErr: fmt.Errorf(errDocumentNotFound, errors.New("'my-item', 'you-cant-find-me.png'")),
  941. },
  942. },
  943. },
  944. {
  945. setupNote: "one vault, one item, two fields w/ same Label",
  946. provider: &ProviderOnePassword{
  947. vaults: map[string]int{myVault: 1},
  948. client: fake.NewMockClient().
  949. AddPredictableVault(myVault).
  950. AddPredictableItemWithField(myVault, myItem, key1, value1).
  951. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  952. Label: key1,
  953. Value: value2,
  954. }),
  955. },
  956. checks: []check{
  957. {
  958. checkNote: key1,
  959. ref: esv1.ExternalSecretDataRemoteRef{
  960. Key: myItem,
  961. Property: key1,
  962. },
  963. expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneField, "'key1' in 'my-item', got 2"),
  964. },
  965. },
  966. },
  967. }
  968. // run the tests
  969. for _, tc := range testCases {
  970. for _, check := range tc.checks {
  971. got, err := tc.provider.GetSecret(context.Background(), check.ref)
  972. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  973. if check.expectedErr == nil && err != nil {
  974. // expected no error, got one
  975. t.Errorf(getSecretErrFormat, notes, nil, err)
  976. }
  977. if check.expectedErr != nil && err == nil {
  978. // expected an error, didn't get one
  979. t.Errorf(getSecretErrFormat, notes, check.expectedErr.Error(), nil)
  980. }
  981. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  982. // expected an error, got the wrong one
  983. t.Errorf(getSecretErrFormat, notes, check.expectedErr.Error(), err.Error())
  984. }
  985. if check.expectedValue != "" {
  986. if check.expectedValue != string(got) {
  987. // expected a predefined value, got something else
  988. t.Errorf(getSecretErrFormat, notes, check.expectedValue, string(got))
  989. }
  990. }
  991. }
  992. }
  993. }
  994. // most functionality is tested in TestFindItem. here we just check:
  995. //
  996. // all keys are fetched and the map is compiled correctly,
  997. // files are loaded, and the data or errors are properly returned.
  998. func TestGetSecretMap(t *testing.T) {
  999. type check struct {
  1000. checkNote string
  1001. ref esv1.ExternalSecretDataRemoteRef
  1002. expectedMap map[string][]byte
  1003. expectedErr error
  1004. }
  1005. type testCase struct {
  1006. setupNote string
  1007. provider *ProviderOnePassword
  1008. checks []check
  1009. }
  1010. testCases := []testCase{
  1011. {
  1012. setupNote: "one vault, one item, two fields",
  1013. provider: &ProviderOnePassword{
  1014. vaults: map[string]int{myVault: 1},
  1015. client: fake.NewMockClient().
  1016. AddPredictableVault(myVault).
  1017. AppendItem(myVaultID, onepassword.Item{
  1018. ID: myItemID,
  1019. Title: myItem,
  1020. Vault: onepassword.ItemVault{ID: myVaultID},
  1021. Files: []*onepassword.File{
  1022. {
  1023. ID: myFilePNGID,
  1024. Name: myFilePNG,
  1025. },
  1026. {
  1027. ID: myFile2ID,
  1028. Name: myFile2PNG,
  1029. },
  1030. },
  1031. }).
  1032. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1033. Label: key1,
  1034. Value: value1,
  1035. }).
  1036. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1037. Label: password,
  1038. Value: value2,
  1039. }).
  1040. SetFileContents(myFilePNG, []byte(myContents)).
  1041. SetFileContents(myFile2PNG, []byte(myContents2)),
  1042. },
  1043. checks: []check{
  1044. {
  1045. checkNote: "all Properties",
  1046. ref: esv1.ExternalSecretDataRemoteRef{
  1047. Key: myItem,
  1048. },
  1049. expectedMap: map[string][]byte{
  1050. key1: []byte(value1),
  1051. password: []byte(value2),
  1052. },
  1053. expectedErr: nil,
  1054. },
  1055. {
  1056. checkNote: "limit by Property",
  1057. ref: esv1.ExternalSecretDataRemoteRef{
  1058. Key: myItem,
  1059. Property: password,
  1060. },
  1061. expectedMap: map[string][]byte{
  1062. password: []byte(value2),
  1063. },
  1064. expectedErr: nil,
  1065. },
  1066. {
  1067. checkNote: "'ref.version' not implemented",
  1068. ref: esv1.ExternalSecretDataRemoteRef{
  1069. Key: myItem,
  1070. Property: key1,
  1071. Version: "123",
  1072. },
  1073. expectedErr: errors.New(errVersionNotImplemented),
  1074. },
  1075. {
  1076. checkNote: "limit by Property with prefix",
  1077. ref: esv1.ExternalSecretDataRemoteRef{
  1078. Key: myItem,
  1079. Property: filePrefix + prefixSplitter + myFilePNG,
  1080. },
  1081. expectedMap: map[string][]byte{
  1082. myFilePNG: []byte(myContents),
  1083. },
  1084. expectedErr: nil,
  1085. },
  1086. },
  1087. },
  1088. {
  1089. setupNote: "files",
  1090. provider: &ProviderOnePassword{
  1091. vaults: map[string]int{myVault: 1},
  1092. client: fake.NewMockClient().
  1093. AddPredictableVault(myVault).
  1094. AppendItem(myVaultID, onepassword.Item{
  1095. ID: myItemID,
  1096. Title: myItem,
  1097. Vault: onepassword.ItemVault{ID: myVaultID},
  1098. Category: documentCategory,
  1099. Files: []*onepassword.File{
  1100. {
  1101. ID: myFilePNGID,
  1102. Name: myFilePNG,
  1103. },
  1104. {
  1105. ID: myFile2ID,
  1106. Name: myFile2PNG,
  1107. },
  1108. },
  1109. }).
  1110. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1111. Label: key1,
  1112. Value: value2,
  1113. }).
  1114. SetFileContents(myFilePNG, []byte(myContents)).
  1115. SetFileContents(myFile2PNG, []byte(myContents2)),
  1116. },
  1117. checks: []check{
  1118. {
  1119. checkNote: "all Properties",
  1120. ref: esv1.ExternalSecretDataRemoteRef{
  1121. Key: myItem,
  1122. },
  1123. expectedMap: map[string][]byte{
  1124. myFilePNG: []byte(myContents),
  1125. myFile2PNG: []byte(myContents2),
  1126. },
  1127. expectedErr: nil,
  1128. },
  1129. {
  1130. checkNote: "limit by Property",
  1131. ref: esv1.ExternalSecretDataRemoteRef{
  1132. Key: myItem,
  1133. Property: myFilePNG,
  1134. },
  1135. expectedMap: map[string][]byte{
  1136. myFilePNG: []byte(myContents),
  1137. },
  1138. expectedErr: nil,
  1139. },
  1140. {
  1141. checkNote: "limit by Property with prefix",
  1142. ref: esv1.ExternalSecretDataRemoteRef{
  1143. Key: myItem,
  1144. Property: filePrefix + prefixSplitter + myFilePNG,
  1145. },
  1146. expectedMap: map[string][]byte{
  1147. myFilePNG: []byte(myContents),
  1148. },
  1149. expectedErr: nil,
  1150. },
  1151. {
  1152. checkNote: "get field limit by Property",
  1153. ref: esv1.ExternalSecretDataRemoteRef{
  1154. Key: myItem,
  1155. Property: fieldPrefix + prefixSplitter + key1,
  1156. },
  1157. expectedMap: map[string][]byte{
  1158. key1: []byte(value2),
  1159. },
  1160. expectedErr: nil,
  1161. },
  1162. },
  1163. },
  1164. {
  1165. setupNote: "one vault, one item, two fields w/ same Label",
  1166. provider: &ProviderOnePassword{
  1167. vaults: map[string]int{myVault: 1},
  1168. client: fake.NewMockClient().
  1169. AddPredictableVault(myVault).
  1170. AddPredictableItemWithField(myVault, myItem, key1, value1).
  1171. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1172. Label: key1,
  1173. Value: value2,
  1174. }),
  1175. },
  1176. checks: []check{
  1177. {
  1178. checkNote: key1,
  1179. ref: esv1.ExternalSecretDataRemoteRef{
  1180. Key: myItem,
  1181. },
  1182. expectedMap: nil,
  1183. expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneField, "'key1' in 'my-item', got 2"),
  1184. },
  1185. },
  1186. },
  1187. }
  1188. // run the tests
  1189. for _, tc := range testCases {
  1190. for _, check := range tc.checks {
  1191. gotMap, err := tc.provider.GetSecretMap(context.Background(), check.ref)
  1192. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  1193. if check.expectedErr == nil && err != nil {
  1194. // expected no error, got one
  1195. t.Errorf(getSecretMapErrFormat, notes, nil, err)
  1196. }
  1197. if check.expectedErr != nil && err == nil {
  1198. // expected an error, didn't get one
  1199. t.Errorf(getSecretMapErrFormat, notes, check.expectedErr.Error(), nil)
  1200. }
  1201. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  1202. // expected an error, got the wrong one
  1203. t.Errorf(getSecretMapErrFormat, notes, check.expectedErr.Error(), err.Error())
  1204. }
  1205. if !reflect.DeepEqual(check.expectedMap, gotMap) {
  1206. // expected a predefined map, got something else
  1207. t.Errorf(getSecretMapErrFormat, notes, check.expectedMap, gotMap)
  1208. }
  1209. }
  1210. }
  1211. }
  1212. func TestGetAllSecrets(t *testing.T) {
  1213. type check struct {
  1214. checkNote string
  1215. ref esv1.ExternalSecretFind
  1216. expectedMap map[string][]byte
  1217. expectedErr error
  1218. }
  1219. type testCase struct {
  1220. setupNote string
  1221. provider *ProviderOnePassword
  1222. checks []check
  1223. }
  1224. testCases := []testCase{
  1225. {
  1226. setupNote: "three vaults, three items, all different field Labels",
  1227. provider: &ProviderOnePassword{
  1228. vaults: map[string]int{myVault: 1, myOtherVault: 2, myNonMatchingVault: 3},
  1229. client: fake.NewMockClient().
  1230. AddPredictableVault(myVault).
  1231. AddPredictableItemWithField(myVault, myItem, key1, value1).
  1232. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1233. Label: key2,
  1234. Value: value2,
  1235. }).
  1236. AddPredictableVault(myOtherVault).
  1237. AddPredictableItemWithField(myOtherVault, myOtherItem, key3, value3).
  1238. AppendItemField(myOtherVaultID, myOtherItemID, onepassword.ItemField{
  1239. Label: key4,
  1240. Value: value4,
  1241. }).
  1242. AddPredictableVault(myNonMatchingVault).
  1243. AddPredictableItemWithField(myNonMatchingVault, myNonMatchingItem, "non-matching5", "value5").
  1244. AppendItemField(myNonMatchingVaultID, myNonMatchingItemID, onepassword.ItemField{
  1245. Label: "non-matching6",
  1246. Value: "value6",
  1247. }),
  1248. },
  1249. checks: []check{
  1250. {
  1251. checkNote: "find some with path only",
  1252. ref: esv1.ExternalSecretFind{
  1253. Path: new(myItem),
  1254. },
  1255. expectedMap: map[string][]byte{
  1256. key1: []byte(value1),
  1257. key2: []byte(value2),
  1258. },
  1259. expectedErr: nil,
  1260. },
  1261. {
  1262. checkNote: "find most with regex 'key*'",
  1263. ref: esv1.ExternalSecretFind{
  1264. Name: &esv1.FindName{
  1265. RegExp: "key*",
  1266. },
  1267. },
  1268. expectedMap: map[string][]byte{
  1269. key1: []byte(value1),
  1270. key2: []byte(value2),
  1271. key3: []byte(value3),
  1272. key4: []byte(value4),
  1273. },
  1274. expectedErr: nil,
  1275. },
  1276. {
  1277. checkNote: "find some with regex 'key*' and path 'my-other-item'",
  1278. ref: esv1.ExternalSecretFind{
  1279. Name: &esv1.FindName{
  1280. RegExp: "key*",
  1281. },
  1282. Path: new(myOtherItem),
  1283. },
  1284. expectedMap: map[string][]byte{
  1285. key3: []byte(value3),
  1286. key4: []byte(value4),
  1287. },
  1288. expectedErr: nil,
  1289. },
  1290. {
  1291. checkNote: "find none with regex 'asdf*'",
  1292. ref: esv1.ExternalSecretFind{
  1293. Name: &esv1.FindName{
  1294. RegExp: "asdf*",
  1295. },
  1296. },
  1297. expectedMap: map[string][]byte{},
  1298. expectedErr: nil,
  1299. },
  1300. {
  1301. checkNote: "find none with path 'no-exist'",
  1302. ref: esv1.ExternalSecretFind{
  1303. Name: &esv1.FindName{
  1304. RegExp: "key*",
  1305. },
  1306. Path: new("no-exist"),
  1307. },
  1308. expectedMap: map[string][]byte{},
  1309. expectedErr: nil,
  1310. },
  1311. },
  1312. },
  1313. {
  1314. setupNote: "one vault, three items, find by tags",
  1315. provider: &ProviderOnePassword{
  1316. vaults: map[string]int{myVault: 1},
  1317. client: fake.NewMockClient().
  1318. AddPredictableVault(myVault).
  1319. AppendItem(myVaultID, onepassword.Item{
  1320. ID: myItemID,
  1321. Title: myItem,
  1322. Tags: []string{"foo", "bar"},
  1323. Vault: onepassword.ItemVault{ID: myVaultID},
  1324. }).
  1325. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1326. Label: key1,
  1327. Value: value1,
  1328. }).
  1329. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1330. Label: key2,
  1331. Value: value2,
  1332. }).
  1333. AppendItem(myVaultID, onepassword.Item{
  1334. ID: "my-item-id-2",
  1335. Title: "my-item-2",
  1336. Vault: onepassword.ItemVault{ID: myVaultID},
  1337. Tags: []string{"foo", "baz"},
  1338. }).
  1339. AppendItemField(myVaultID, "my-item-id-2", onepassword.ItemField{
  1340. Label: key3,
  1341. Value: value3,
  1342. }).
  1343. AppendItem(myVaultID, onepassword.Item{
  1344. ID: "my-item-id-3",
  1345. Title: "my-item-3",
  1346. Vault: onepassword.ItemVault{ID: myVaultID},
  1347. Tags: []string{"bang", "bing"},
  1348. }).
  1349. AppendItemField(myVaultID, "my-item-id-3", onepassword.ItemField{
  1350. Label: key4,
  1351. Value: value4,
  1352. }),
  1353. },
  1354. checks: []check{
  1355. {
  1356. checkNote: "find with tags",
  1357. ref: esv1.ExternalSecretFind{
  1358. Path: new(myItem),
  1359. Tags: map[string]string{
  1360. "foo": "true",
  1361. "bar": "true",
  1362. },
  1363. },
  1364. expectedMap: map[string][]byte{
  1365. key1: []byte(value1),
  1366. key2: []byte(value2),
  1367. },
  1368. expectedErr: nil,
  1369. },
  1370. {
  1371. checkNote: "find with tags and get all",
  1372. ref: esv1.ExternalSecretFind{
  1373. Path: new(myItem),
  1374. Tags: map[string]string{
  1375. "foo": "true",
  1376. },
  1377. },
  1378. expectedMap: map[string][]byte{
  1379. key1: []byte(value1),
  1380. key2: []byte(value2),
  1381. key3: []byte(value3),
  1382. },
  1383. expectedErr: nil,
  1384. },
  1385. },
  1386. },
  1387. {
  1388. setupNote: "3 vaults, 4 items, 5 files",
  1389. provider: &ProviderOnePassword{
  1390. vaults: map[string]int{myVault: 1, myOtherVault: 2, myNonMatchingVault: 3},
  1391. client: fake.NewMockClient().
  1392. // my-vault
  1393. AddPredictableVault(myVault).
  1394. AppendItem(myVaultID, onepassword.Item{
  1395. ID: myItemID,
  1396. Title: myItem,
  1397. Vault: onepassword.ItemVault{ID: myVaultID},
  1398. Category: documentCategory,
  1399. Files: []*onepassword.File{
  1400. {
  1401. ID: myFilePNGID,
  1402. Name: myFilePNG,
  1403. },
  1404. {
  1405. ID: mySecondFileTXTID,
  1406. Name: mySecondFileTXT,
  1407. },
  1408. },
  1409. }).
  1410. SetFileContents(myFilePNG, []byte(myContents)).
  1411. SetFileContents(mySecondFileTXT, []byte(mySecondContents)).
  1412. AppendItem(myVaultID, onepassword.Item{
  1413. ID: "my-item-2-id",
  1414. Title: "my-item-2",
  1415. Vault: onepassword.ItemVault{ID: myVaultID},
  1416. Category: documentCategory,
  1417. Files: []*onepassword.File{
  1418. {
  1419. ID: myFile2ID,
  1420. Name: myFile2TXT,
  1421. },
  1422. },
  1423. }).
  1424. SetFileContents(myFile2TXT, []byte(myContents2)).
  1425. // my-other-vault
  1426. AddPredictableVault(myOtherVault).
  1427. AppendItem(myOtherVaultID, onepassword.Item{
  1428. ID: myOtherItemID,
  1429. Title: myOtherItem,
  1430. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  1431. Category: documentCategory,
  1432. Files: []*onepassword.File{
  1433. {
  1434. ID: myOtherFilePNGID,
  1435. Name: myOtherFilePNG,
  1436. },
  1437. },
  1438. }).
  1439. SetFileContents(myOtherFilePNG, []byte(myOtherContents)).
  1440. // my-non-matching-vault
  1441. AddPredictableVault(myNonMatchingVault).
  1442. AppendItem(myNonMatchingVaultID, onepassword.Item{
  1443. ID: myNonMatchingItemID,
  1444. Title: myNonMatchingItem,
  1445. Vault: onepassword.ItemVault{ID: myNonMatchingVaultID},
  1446. Category: documentCategory,
  1447. Files: []*onepassword.File{
  1448. {
  1449. ID: nonMatchingFilePNGID,
  1450. Name: nonMatchingFilePNG,
  1451. },
  1452. },
  1453. }).
  1454. SetFileContents(nonMatchingFilePNG, []byte(nonMatchingContents)),
  1455. },
  1456. checks: []check{
  1457. {
  1458. checkNote: "find most with regex '^my-*'",
  1459. ref: esv1.ExternalSecretFind{
  1460. Name: &esv1.FindName{
  1461. RegExp: "^my-*",
  1462. },
  1463. },
  1464. expectedMap: map[string][]byte{
  1465. myFilePNG: []byte(myContents),
  1466. mySecondFileTXT: []byte(mySecondContents),
  1467. myFile2TXT: []byte(myContents2),
  1468. myOtherFilePNG: []byte(myOtherContents),
  1469. },
  1470. expectedErr: nil,
  1471. },
  1472. {
  1473. checkNote: "find some with regex '^my-*' and path 'my-other-item'",
  1474. ref: esv1.ExternalSecretFind{
  1475. Name: &esv1.FindName{
  1476. RegExp: "^my-*",
  1477. },
  1478. Path: new(myOtherItem),
  1479. },
  1480. expectedMap: map[string][]byte{
  1481. myOtherFilePNG: []byte(myOtherContents),
  1482. },
  1483. expectedErr: nil,
  1484. },
  1485. {
  1486. checkNote: "find none with regex '^asdf*'",
  1487. ref: esv1.ExternalSecretFind{
  1488. Name: &esv1.FindName{
  1489. RegExp: "^asdf*",
  1490. },
  1491. },
  1492. expectedMap: map[string][]byte{},
  1493. expectedErr: nil,
  1494. },
  1495. {
  1496. checkNote: "find none with path 'no-exist'",
  1497. ref: esv1.ExternalSecretFind{
  1498. Name: &esv1.FindName{
  1499. RegExp: "^my-*",
  1500. },
  1501. Path: new("no-exist"),
  1502. },
  1503. expectedMap: map[string][]byte{},
  1504. expectedErr: nil,
  1505. },
  1506. },
  1507. },
  1508. {
  1509. setupNote: "two fields/files with same name, first one wins",
  1510. provider: &ProviderOnePassword{
  1511. vaults: map[string]int{myVault: 1, myOtherVault: 2},
  1512. client: fake.NewMockClient().
  1513. // my-vault
  1514. AddPredictableVault(myVault).
  1515. AddPredictableItemWithField(myVault, myItem, key1, value1).
  1516. AddPredictableItemWithField(myVault, "my-second-item", key1, "value-second").
  1517. AppendItem(myVaultID, onepassword.Item{
  1518. ID: "file-item-id",
  1519. Title: "file-item",
  1520. Vault: onepassword.ItemVault{ID: myVaultID},
  1521. Category: documentCategory,
  1522. Files: []*onepassword.File{
  1523. {
  1524. ID: filePNGID,
  1525. Name: filePNG,
  1526. },
  1527. },
  1528. }).
  1529. SetFileContents(filePNG, []byte(myContents)).
  1530. AppendItem(myVaultID, onepassword.Item{
  1531. ID: "file-item-2-id",
  1532. Title: "file-item-2",
  1533. Vault: onepassword.ItemVault{ID: myVaultID},
  1534. Category: documentCategory,
  1535. Files: []*onepassword.File{
  1536. {
  1537. ID: "file-2-id",
  1538. Name: filePNG,
  1539. },
  1540. },
  1541. }).
  1542. // my-other-vault
  1543. AddPredictableVault(myOtherVault).
  1544. AddPredictableItemWithField(myOtherVault, myOtherItem, key1, "value-other").
  1545. AppendItem(myOtherVaultID, onepassword.Item{
  1546. ID: "file-item-other-id",
  1547. Title: "file-item-other",
  1548. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  1549. Category: documentCategory,
  1550. Files: []*onepassword.File{
  1551. {
  1552. ID: "other-file-id",
  1553. Name: filePNG,
  1554. },
  1555. },
  1556. }),
  1557. },
  1558. checks: []check{
  1559. {
  1560. checkNote: "find fields with regex '^key*'",
  1561. ref: esv1.ExternalSecretFind{
  1562. Name: &esv1.FindName{
  1563. RegExp: "^key*",
  1564. },
  1565. },
  1566. expectedMap: map[string][]byte{
  1567. key1: []byte(value1),
  1568. },
  1569. expectedErr: nil,
  1570. },
  1571. {
  1572. checkNote: "find files with regex '^file*item*'",
  1573. ref: esv1.ExternalSecretFind{
  1574. Name: &esv1.FindName{
  1575. RegExp: "^file*",
  1576. },
  1577. },
  1578. expectedMap: map[string][]byte{
  1579. filePNG: []byte(myContents),
  1580. },
  1581. expectedErr: nil,
  1582. },
  1583. },
  1584. },
  1585. }
  1586. // run the tests
  1587. for _, tc := range testCases {
  1588. for _, check := range tc.checks {
  1589. gotMap, err := tc.provider.GetAllSecrets(context.Background(), check.ref)
  1590. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  1591. if check.expectedErr == nil && err != nil {
  1592. // expected no error, got one
  1593. t.Fatalf(getAllSecretsErrFormat, notes, nil, err)
  1594. }
  1595. if check.expectedErr != nil && err == nil {
  1596. // expected an error, didn't get one
  1597. t.Errorf(getAllSecretsErrFormat, notes, check.expectedErr.Error(), nil)
  1598. }
  1599. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  1600. // expected an error, got the wrong one
  1601. t.Errorf(getAllSecretsErrFormat, notes, check.expectedErr.Error(), err.Error())
  1602. }
  1603. if !reflect.DeepEqual(check.expectedMap, gotMap) {
  1604. // expected a predefined map, got something else
  1605. t.Errorf(getAllSecretsErrFormat, notes, check.expectedMap, gotMap)
  1606. }
  1607. }
  1608. }
  1609. }
  1610. func TestSortVaults(t *testing.T) {
  1611. type testCase struct {
  1612. vaults map[string]int
  1613. expected []string
  1614. }
  1615. testCases := []testCase{
  1616. {
  1617. vaults: map[string]int{
  1618. one: 1,
  1619. three: 3,
  1620. two: 2,
  1621. },
  1622. expected: []string{
  1623. one,
  1624. two,
  1625. three,
  1626. },
  1627. },
  1628. {
  1629. vaults: map[string]int{
  1630. "four": 100,
  1631. one: 1,
  1632. three: 3,
  1633. two: 2,
  1634. },
  1635. expected: []string{
  1636. one,
  1637. two,
  1638. three,
  1639. "four",
  1640. },
  1641. },
  1642. }
  1643. // run the tests
  1644. for _, tc := range testCases {
  1645. got := sortVaults(tc.vaults)
  1646. if !reflect.DeepEqual(got, tc.expected) {
  1647. t.Errorf("onepassword.sortVaults(...): -expected, +got:\n-%#v\n+%#v\n", tc.expected, got)
  1648. }
  1649. }
  1650. }
  1651. func TestIsNativeItemID(t *testing.T) {
  1652. tests := []struct {
  1653. name string
  1654. input string
  1655. expected bool
  1656. }{
  1657. {"valid native ID", "gdpvdudxrico74msloimk7qjna", true},
  1658. {"valid native ID all letters", "abcdefghijklmnopqrstuvwxyz", true},
  1659. {"valid native ID with digits", "abcdefghij0123456789abcdef", true},
  1660. {"too short", "gdpvdudxrico74msloimk7qjn", false},
  1661. {"too long", "gdpvdudxrico74msloimk7qjnaa", false},
  1662. {"empty string", "", false},
  1663. {"contains uppercase", "Gdpvdudxrico74msloimk7qjna", false},
  1664. {"contains special char", "gdpvdudxrico7-msloimk7qjna", false},
  1665. {"RFC 4122 UUID", "687adbe7-e6d2-4059-9a62-dbb95d291143", false},
  1666. {"item title", "My App (Production)", false},
  1667. }
  1668. for _, tt := range tests {
  1669. t.Run(tt.name, func(t *testing.T) {
  1670. got := isNativeItemID(tt.input)
  1671. if got != tt.expected {
  1672. t.Errorf("isNativeItemID(%q) = %v, want %v", tt.input, got, tt.expected)
  1673. }
  1674. })
  1675. }
  1676. }
  1677. func TestHasUniqueVaultNumbers(t *testing.T) {
  1678. type testCase struct {
  1679. vaults map[string]int
  1680. expected bool
  1681. }
  1682. testCases := []testCase{
  1683. {
  1684. vaults: map[string]int{
  1685. one: 1,
  1686. three: 3,
  1687. two: 2,
  1688. },
  1689. expected: true,
  1690. },
  1691. {
  1692. vaults: map[string]int{
  1693. "four": 100,
  1694. one: 1,
  1695. three: 3,
  1696. two: 2,
  1697. "eight": 100,
  1698. },
  1699. expected: false,
  1700. },
  1701. {
  1702. vaults: map[string]int{
  1703. one: 1,
  1704. "1": 1,
  1705. three: 3,
  1706. two: 2,
  1707. },
  1708. expected: false,
  1709. },
  1710. }
  1711. // run the tests
  1712. for _, tc := range testCases {
  1713. got := hasUniqueVaultNumbers(tc.vaults)
  1714. if got != tc.expected {
  1715. t.Errorf("onepassword.hasUniqueVaultNumbers(...): -expected, +got:\n-%#v\n+%#v\n", tc.expected, got)
  1716. }
  1717. }
  1718. }
  1719. type fakeRef struct {
  1720. key string
  1721. prop string
  1722. secretKey string
  1723. metadata *apiextensionsv1.JSON
  1724. }
  1725. func (f fakeRef) GetRemoteKey() string {
  1726. return f.key
  1727. }
  1728. func (f fakeRef) GetProperty() string {
  1729. return f.prop
  1730. }
  1731. func (f fakeRef) GetSecretKey() string {
  1732. return f.secretKey
  1733. }
  1734. func (f fakeRef) GetMetadata() *apiextensionsv1.JSON {
  1735. return f.metadata
  1736. }
  1737. func validateItem(t *testing.T, expectedItem, actualItem *onepassword.Item) {
  1738. t.Helper()
  1739. if !reflect.DeepEqual(expectedItem, actualItem) {
  1740. t.Errorf("expected item %v, got %v", expectedItem, actualItem)
  1741. }
  1742. }
  1743. func TestProviderOnePasswordCreateItem(t *testing.T) {
  1744. type testCase struct {
  1745. vaults map[string]int
  1746. expectedErr error
  1747. setupNote string
  1748. val []byte
  1749. createValidateFunc func(*testing.T, *onepassword.Item, string) (*onepassword.Item, error)
  1750. ref esv1.PushSecretData
  1751. }
  1752. const vaultName = "vault1"
  1753. const fallbackVaultName = "vault2"
  1754. thridPartyErr := errors.New("third party error")
  1755. metadata := &metadata.PushSecretMetadata[PushSecretMetadataSpec]{
  1756. APIVersion: metadata.APIVersion,
  1757. Kind: metadata.Kind,
  1758. Spec: PushSecretMetadataSpec{
  1759. Tags: []string{"tag1", "tag2"},
  1760. Vault: fallbackVaultName,
  1761. },
  1762. }
  1763. metadataRaw, _ := json.Marshal(metadata)
  1764. testCases := []testCase{
  1765. {
  1766. setupNote: "standard create",
  1767. val: []byte("value"),
  1768. ref: fakeRef{
  1769. key: "testing",
  1770. prop: "prop",
  1771. },
  1772. expectedErr: nil,
  1773. vaults: map[string]int{
  1774. vaultName: 1,
  1775. fallbackVaultName: 2,
  1776. },
  1777. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1778. validateItem(t, &onepassword.Item{
  1779. Title: "testing",
  1780. Category: onepassword.Server,
  1781. Vault: onepassword.ItemVault{
  1782. ID: vaultName,
  1783. },
  1784. Fields: []*onepassword.ItemField{
  1785. generateNewItemField("prop", "value"),
  1786. },
  1787. }, item)
  1788. return item, nil
  1789. },
  1790. },
  1791. {
  1792. setupNote: "standard create with no property",
  1793. val: []byte("value2"),
  1794. ref: fakeRef{
  1795. key: "testing2",
  1796. prop: "",
  1797. },
  1798. vaults: map[string]int{
  1799. vaultName: 2,
  1800. },
  1801. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1802. validateItem(t, &onepassword.Item{
  1803. Title: "testing2",
  1804. Category: onepassword.Server,
  1805. Vault: onepassword.ItemVault{
  1806. ID: vaultName,
  1807. },
  1808. Fields: []*onepassword.ItemField{
  1809. generateNewItemField("password", "value2"),
  1810. },
  1811. }, item)
  1812. return item, nil
  1813. },
  1814. },
  1815. {
  1816. setupNote: "no vaults",
  1817. val: []byte("value"),
  1818. ref: fakeRef{
  1819. key: "testing",
  1820. prop: "prop",
  1821. },
  1822. vaults: map[string]int{},
  1823. expectedErr: ErrNoVaults,
  1824. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1825. t.Errorf("onepassword.createItem(...): should not have been called")
  1826. return nil, nil
  1827. },
  1828. },
  1829. {
  1830. setupNote: "error on create",
  1831. val: []byte("testing"),
  1832. ref: fakeRef{
  1833. key: "another",
  1834. prop: "property",
  1835. },
  1836. vaults: map[string]int{
  1837. vaultName: 1,
  1838. },
  1839. expectedErr: thridPartyErr,
  1840. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1841. validateItem(t, &onepassword.Item{
  1842. Title: "another",
  1843. Category: onepassword.Server,
  1844. Vault: onepassword.ItemVault{
  1845. ID: vaultName,
  1846. },
  1847. Fields: []*onepassword.ItemField{
  1848. generateNewItemField("property", "testing"),
  1849. },
  1850. }, item)
  1851. return nil, thridPartyErr
  1852. },
  1853. },
  1854. {
  1855. setupNote: "valid metadata overrides",
  1856. val: []byte("testing"),
  1857. ref: fakeRef{
  1858. key: "another",
  1859. prop: "property",
  1860. metadata: &apiextensionsv1.JSON{
  1861. Raw: metadataRaw,
  1862. },
  1863. },
  1864. vaults: map[string]int{
  1865. vaultName: 1,
  1866. fallbackVaultName: 2,
  1867. },
  1868. expectedErr: nil,
  1869. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1870. validateItem(t, &onepassword.Item{
  1871. Title: "another",
  1872. Category: onepassword.Server,
  1873. Vault: onepassword.ItemVault{
  1874. ID: fallbackVaultName,
  1875. },
  1876. Fields: []*onepassword.ItemField{
  1877. generateNewItemField("property", "testing"),
  1878. },
  1879. Tags: []string{"tag1", "tag2"},
  1880. }, item)
  1881. return item, nil
  1882. },
  1883. },
  1884. }
  1885. provider := &ProviderOnePassword{}
  1886. for _, tc := range testCases {
  1887. // setup
  1888. mockClient := fake.NewMockClient()
  1889. mockClient.CreateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  1890. i, e := tc.createValidateFunc(t, item, s)
  1891. return i, e
  1892. }
  1893. provider.client = mockClient
  1894. provider.vaults = tc.vaults
  1895. err := provider.createItem(tc.val, tc.ref)
  1896. if !errors.Is(err, tc.expectedErr) {
  1897. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  1898. }
  1899. }
  1900. }
  1901. func TestProviderOnePasswordDeleteItem(t *testing.T) {
  1902. type testCase struct {
  1903. inputFields []*onepassword.ItemField
  1904. fieldName string
  1905. expectedErr error
  1906. expectedFields []*onepassword.ItemField
  1907. setupNote string
  1908. }
  1909. field1, field2, field3, field4 := "field1", "field2", "field3", "field4"
  1910. testCases := []testCase{
  1911. {
  1912. setupNote: "one field to remove",
  1913. inputFields: []*onepassword.ItemField{
  1914. {
  1915. ID: field1,
  1916. Label: field1,
  1917. Type: onepassword.FieldTypeAddress,
  1918. },
  1919. {
  1920. ID: field2,
  1921. Label: field2,
  1922. Type: onepassword.FieldTypeString,
  1923. },
  1924. {
  1925. ID: field3,
  1926. Label: field3,
  1927. Type: onepassword.FieldTypeConcealed,
  1928. },
  1929. },
  1930. fieldName: field2,
  1931. expectedFields: []*onepassword.ItemField{
  1932. {
  1933. ID: field1,
  1934. Label: field1,
  1935. Type: onepassword.FieldTypeAddress,
  1936. },
  1937. {
  1938. ID: field3,
  1939. Label: field3,
  1940. Type: onepassword.FieldTypeConcealed,
  1941. },
  1942. },
  1943. },
  1944. {
  1945. setupNote: "no fields to remove",
  1946. inputFields: []*onepassword.ItemField{
  1947. {
  1948. ID: field1,
  1949. Label: field1,
  1950. Type: onepassword.FieldTypeAddress,
  1951. },
  1952. {
  1953. ID: field2,
  1954. Label: field2,
  1955. Type: onepassword.FieldTypeString,
  1956. },
  1957. {
  1958. ID: field3,
  1959. Label: field3,
  1960. Type: onepassword.FieldTypeConcealed,
  1961. },
  1962. },
  1963. expectedErr: nil,
  1964. fieldName: field4,
  1965. expectedFields: []*onepassword.ItemField{
  1966. {
  1967. ID: field1,
  1968. Label: field1,
  1969. Type: onepassword.FieldTypeAddress,
  1970. },
  1971. {
  1972. ID: field2,
  1973. Label: field2,
  1974. Type: onepassword.FieldTypeString,
  1975. },
  1976. {
  1977. ID: field3,
  1978. Label: field3,
  1979. Type: onepassword.FieldTypeConcealed,
  1980. },
  1981. },
  1982. },
  1983. {
  1984. setupNote: "multiple fields to remove",
  1985. inputFields: []*onepassword.ItemField{
  1986. {
  1987. ID: field3,
  1988. Label: field3,
  1989. Type: onepassword.FieldTypeConcealed,
  1990. },
  1991. {
  1992. ID: field1,
  1993. Label: field1,
  1994. Type: onepassword.FieldTypeAddress,
  1995. },
  1996. {
  1997. ID: field3,
  1998. Label: field3,
  1999. Type: onepassword.FieldTypeCreditCardType,
  2000. },
  2001. {
  2002. ID: field2,
  2003. Label: field2,
  2004. Type: onepassword.FieldTypeString,
  2005. },
  2006. {
  2007. ID: field3,
  2008. Label: field3,
  2009. Type: onepassword.FieldTypeGender,
  2010. },
  2011. },
  2012. fieldName: field3,
  2013. expectedErr: ErrExpectedOneField,
  2014. expectedFields: nil,
  2015. },
  2016. }
  2017. // run the tests
  2018. for _, tc := range testCases {
  2019. actualOutput, err := deleteField(tc.inputFields, tc.fieldName)
  2020. if len(actualOutput) != len(tc.expectedFields) {
  2021. t.Errorf("%s: length fields did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, tc.expectedFields, actualOutput)
  2022. return
  2023. }
  2024. if !errors.Is(err, tc.expectedErr) {
  2025. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  2026. }
  2027. for i, check := range tc.expectedFields {
  2028. if len(actualOutput) <= i {
  2029. continue
  2030. }
  2031. if !reflect.DeepEqual(check, actualOutput[i]) {
  2032. t.Errorf("%s: fields at position %d did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, i, check, actualOutput[i])
  2033. }
  2034. }
  2035. }
  2036. }
  2037. func TestUpdateFields(t *testing.T) {
  2038. type testCase struct {
  2039. inputFields []*onepassword.ItemField
  2040. fieldName string
  2041. newVal string
  2042. expectedErr error
  2043. expectedFields []*onepassword.ItemField
  2044. setupNote string
  2045. }
  2046. field1, field2, field3, field4 := "field1", "field2", "field3", "field4"
  2047. testCases := []testCase{
  2048. {
  2049. setupNote: "one field to update",
  2050. inputFields: []*onepassword.ItemField{
  2051. {
  2052. ID: field1,
  2053. Label: field1,
  2054. Value: value1,
  2055. Type: onepassword.FieldTypeAddress,
  2056. },
  2057. {
  2058. ID: field2,
  2059. Label: field2,
  2060. Value: value2,
  2061. Type: onepassword.FieldTypeString,
  2062. },
  2063. {
  2064. ID: field3,
  2065. Label: field3,
  2066. Value: value3,
  2067. Type: onepassword.FieldTypeConcealed,
  2068. },
  2069. },
  2070. fieldName: field2,
  2071. newVal: "testing",
  2072. expectedFields: []*onepassword.ItemField{
  2073. {
  2074. ID: field1,
  2075. Label: field1,
  2076. Value: value1,
  2077. Type: onepassword.FieldTypeAddress,
  2078. },
  2079. {
  2080. ID: field2,
  2081. Label: field2,
  2082. Value: "testing",
  2083. Type: onepassword.FieldTypeString,
  2084. },
  2085. {
  2086. ID: field3,
  2087. Label: field3,
  2088. Value: value3,
  2089. Type: onepassword.FieldTypeConcealed,
  2090. },
  2091. },
  2092. },
  2093. {
  2094. setupNote: "add field",
  2095. inputFields: []*onepassword.ItemField{
  2096. {
  2097. ID: field1,
  2098. Value: value1,
  2099. Label: field1,
  2100. Type: onepassword.FieldTypeAddress,
  2101. },
  2102. {
  2103. ID: field2,
  2104. Label: field2,
  2105. Value: value2,
  2106. Type: onepassword.FieldTypeString,
  2107. },
  2108. },
  2109. fieldName: field4,
  2110. newVal: value4,
  2111. expectedFields: []*onepassword.ItemField{
  2112. {
  2113. ID: field1,
  2114. Label: field1,
  2115. Value: value1,
  2116. Type: onepassword.FieldTypeAddress,
  2117. },
  2118. {
  2119. ID: field2,
  2120. Label: field2,
  2121. Value: value2,
  2122. Type: onepassword.FieldTypeString,
  2123. },
  2124. {
  2125. Label: field4,
  2126. Value: value4,
  2127. Type: onepassword.FieldTypeConcealed,
  2128. },
  2129. },
  2130. },
  2131. {
  2132. setupNote: "no changes",
  2133. inputFields: []*onepassword.ItemField{
  2134. {
  2135. ID: field1,
  2136. Label: field1,
  2137. Value: value1,
  2138. Type: onepassword.FieldTypeAddress,
  2139. },
  2140. {
  2141. ID: field2,
  2142. Label: field2,
  2143. Value: value2,
  2144. Type: onepassword.FieldTypeString,
  2145. },
  2146. },
  2147. fieldName: field1,
  2148. newVal: value1,
  2149. expectedErr: nil,
  2150. expectedFields: []*onepassword.ItemField{
  2151. {
  2152. ID: field1,
  2153. Label: field1,
  2154. Value: value1,
  2155. Type: onepassword.FieldTypeAddress,
  2156. },
  2157. {
  2158. ID: field2,
  2159. Label: field2,
  2160. Value: value2,
  2161. Type: onepassword.FieldTypeString,
  2162. },
  2163. },
  2164. },
  2165. {
  2166. setupNote: "multiple fields to remove",
  2167. inputFields: []*onepassword.ItemField{
  2168. {
  2169. ID: field3,
  2170. Label: field3,
  2171. Value: value3,
  2172. Type: onepassword.FieldTypeConcealed,
  2173. },
  2174. {
  2175. ID: field1,
  2176. Label: field1,
  2177. Value: value1,
  2178. Type: onepassword.FieldTypeAddress,
  2179. },
  2180. {
  2181. ID: field3,
  2182. Label: field3,
  2183. Value: value3,
  2184. Type: onepassword.FieldTypeCreditCardType,
  2185. },
  2186. {
  2187. ID: field2,
  2188. Label: field2,
  2189. Value: value2,
  2190. Type: onepassword.FieldTypeString,
  2191. },
  2192. {
  2193. ID: field3,
  2194. Label: field3,
  2195. Value: value3,
  2196. Type: onepassword.FieldTypeGender,
  2197. },
  2198. },
  2199. fieldName: field3,
  2200. expectedErr: ErrExpectedOneField,
  2201. expectedFields: nil,
  2202. },
  2203. }
  2204. // run the tests
  2205. for _, tc := range testCases {
  2206. actualOutput, err := updateFieldValue(tc.inputFields, tc.fieldName, tc.newVal)
  2207. if len(actualOutput) != len(tc.expectedFields) {
  2208. t.Errorf("%s: length fields did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, tc.expectedFields, actualOutput)
  2209. return
  2210. }
  2211. if !errors.Is(err, tc.expectedErr) {
  2212. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  2213. }
  2214. for i, check := range tc.expectedFields {
  2215. if len(actualOutput) <= i {
  2216. continue
  2217. }
  2218. if !reflect.DeepEqual(check, actualOutput[i]) {
  2219. t.Errorf("%s: fields at position %d did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, i, check, actualOutput[i])
  2220. }
  2221. }
  2222. }
  2223. }
  2224. func TestGenerateNewItemField(t *testing.T) {
  2225. field := generateNewItemField("property", "testing")
  2226. if !reflect.DeepEqual(field, &onepassword.ItemField{
  2227. Label: "property",
  2228. Type: onepassword.FieldTypeConcealed,
  2229. Value: "testing",
  2230. }) {
  2231. t.Errorf("field did not match: -expected, +got:\n-%#v\n+%#v\n", &onepassword.ItemField{
  2232. Label: "property",
  2233. Type: onepassword.FieldTypeConcealed,
  2234. Value: "testing",
  2235. }, field)
  2236. }
  2237. }
  2238. func TestProviderOnePasswordPushSecret(t *testing.T) {
  2239. // Most logic is tested in the createItem and updateField functions
  2240. // This test is just to make sure the correct functions are called.
  2241. // the correct values are passed to them, and errors are propagated
  2242. type testCase struct {
  2243. vaults map[string]int
  2244. expectedErr error
  2245. setupNote string
  2246. existingItems []onepassword.Item
  2247. val *corev1.Secret
  2248. existingItemsFields map[string][]*onepassword.ItemField
  2249. createValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
  2250. updateValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
  2251. ref fakeRef
  2252. }
  2253. var (
  2254. vaultName = "vault1"
  2255. vault = onepassword.Vault{
  2256. ID: vaultName,
  2257. }
  2258. )
  2259. metadata := &metadata.PushSecretMetadata[PushSecretMetadataSpec]{
  2260. APIVersion: metadata.APIVersion,
  2261. Kind: metadata.Kind,
  2262. Spec: PushSecretMetadataSpec{
  2263. Tags: []string{"tag1", "tag2"},
  2264. },
  2265. }
  2266. metadataRaw, _ := json.Marshal(metadata)
  2267. testCases := []testCase{
  2268. {
  2269. vaults: map[string]int{
  2270. vaultName: 1,
  2271. },
  2272. expectedErr: ErrExpectedOneItem,
  2273. setupNote: "find item error",
  2274. existingItems: []onepassword.Item{
  2275. {
  2276. Title: key1,
  2277. }, {
  2278. Title: key1,
  2279. }, // can be empty, testing for error with length
  2280. },
  2281. ref: fakeRef{
  2282. key: key1,
  2283. secretKey: key1,
  2284. },
  2285. val: &corev1.Secret{Data: map[string][]byte{key1: []byte("testing")}},
  2286. },
  2287. {
  2288. setupNote: "create item error",
  2289. expectedErr: ErrNoVaults,
  2290. val: &corev1.Secret{Data: map[string][]byte{key1: []byte("testing")}},
  2291. ref: fakeRef{secretKey: key1},
  2292. vaults: nil,
  2293. },
  2294. {
  2295. setupNote: "key not in data",
  2296. expectedErr: ErrKeyNotFound,
  2297. val: &corev1.Secret{Data: map[string][]byte{}},
  2298. ref: fakeRef{secretKey: key1},
  2299. vaults: nil,
  2300. },
  2301. {
  2302. setupNote: "create item success",
  2303. expectedErr: nil,
  2304. val: &corev1.Secret{Data: map[string][]byte{
  2305. key1: []byte("testing"),
  2306. }},
  2307. ref: fakeRef{
  2308. key: key1,
  2309. prop: "prop",
  2310. secretKey: key1,
  2311. },
  2312. vaults: map[string]int{
  2313. vaultName: 1,
  2314. },
  2315. createValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2316. validateItem(t, &onepassword.Item{
  2317. Title: key1,
  2318. Category: onepassword.Server,
  2319. Vault: onepassword.ItemVault{
  2320. ID: vaultName,
  2321. },
  2322. Fields: []*onepassword.ItemField{
  2323. generateNewItemField("prop", "testing"),
  2324. },
  2325. }, item)
  2326. return item, nil
  2327. },
  2328. },
  2329. {
  2330. setupNote: "update fields error",
  2331. expectedErr: ErrExpectedOneField,
  2332. val: &corev1.Secret{Data: map[string][]byte{
  2333. "key2": []byte("testing"),
  2334. }},
  2335. ref: fakeRef{
  2336. key: key1,
  2337. prop: "prop",
  2338. secretKey: "key2",
  2339. },
  2340. vaults: map[string]int{
  2341. vaultName: 1,
  2342. },
  2343. existingItemsFields: map[string][]*onepassword.ItemField{
  2344. key1: {
  2345. {
  2346. Label: "prop",
  2347. },
  2348. {
  2349. Label: "prop",
  2350. },
  2351. },
  2352. },
  2353. existingItems: []onepassword.Item{
  2354. {
  2355. Vault: onepassword.ItemVault{
  2356. ID: vaultName,
  2357. },
  2358. ID: key1,
  2359. Title: key1,
  2360. },
  2361. },
  2362. },
  2363. {
  2364. setupNote: "standard update",
  2365. expectedErr: nil,
  2366. val: &corev1.Secret{Data: map[string][]byte{
  2367. "key3": []byte("testing2"),
  2368. }},
  2369. ref: fakeRef{
  2370. key: key1,
  2371. prop: "",
  2372. secretKey: "key3",
  2373. },
  2374. vaults: map[string]int{
  2375. vaultName: 1,
  2376. },
  2377. existingItemsFields: map[string][]*onepassword.ItemField{
  2378. key1: {
  2379. {
  2380. Label: "not-prop",
  2381. },
  2382. },
  2383. },
  2384. updateValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2385. expectedItem := &onepassword.Item{
  2386. Vault: onepassword.ItemVault{
  2387. ID: vaultName,
  2388. },
  2389. ID: key1,
  2390. Title: key1,
  2391. Fields: []*onepassword.ItemField{
  2392. {
  2393. Label: "not-prop",
  2394. },
  2395. {
  2396. Label: "password",
  2397. Value: "testing2",
  2398. Type: onepassword.FieldTypeConcealed,
  2399. },
  2400. },
  2401. }
  2402. validateItem(t, expectedItem, item)
  2403. return expectedItem, nil
  2404. },
  2405. existingItems: []onepassword.Item{
  2406. {
  2407. Vault: onepassword.ItemVault{
  2408. ID: vaultName,
  2409. },
  2410. ID: key1,
  2411. Title: key1,
  2412. },
  2413. },
  2414. },
  2415. {
  2416. setupNote: "create item with metadata overwrites success",
  2417. expectedErr: nil,
  2418. val: &corev1.Secret{Data: map[string][]byte{
  2419. key1: []byte("testing"),
  2420. }},
  2421. ref: fakeRef{
  2422. key: key1,
  2423. prop: "prop",
  2424. secretKey: key1,
  2425. metadata: &apiextensionsv1.JSON{
  2426. Raw: metadataRaw,
  2427. },
  2428. },
  2429. vaults: map[string]int{
  2430. vaultName: 1,
  2431. },
  2432. createValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2433. validateItem(t, &onepassword.Item{
  2434. Title: key1,
  2435. Category: onepassword.Server,
  2436. Vault: onepassword.ItemVault{
  2437. ID: vaultName,
  2438. },
  2439. Fields: []*onepassword.ItemField{
  2440. generateNewItemField("prop", "testing"),
  2441. },
  2442. Tags: []string{"tag1", "tag2"},
  2443. }, item)
  2444. return item, nil
  2445. },
  2446. },
  2447. }
  2448. provider := &ProviderOnePassword{}
  2449. for _, tc := range testCases {
  2450. t.Run(tc.setupNote, func(t *testing.T) {
  2451. // setup
  2452. mockClient := fake.NewMockClient()
  2453. mockClient.MockVaults = map[string][]onepassword.Vault{
  2454. vaultName: {vault},
  2455. }
  2456. mockClient.MockItems = map[string][]onepassword.Item{
  2457. vaultName: tc.existingItems,
  2458. }
  2459. mockClient.MockItemFields = map[string]map[string][]*onepassword.ItemField{
  2460. vaultName: tc.existingItemsFields,
  2461. }
  2462. mockClient.CreateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2463. return tc.createValidateFunc(item, s)
  2464. }
  2465. mockClient.UpdateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2466. return tc.updateValidateFunc(item, s)
  2467. }
  2468. provider.client = mockClient
  2469. provider.vaults = tc.vaults
  2470. err := provider.PushSecret(context.Background(), tc.val, tc.ref)
  2471. if !errors.Is(err, tc.expectedErr) {
  2472. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  2473. }
  2474. })
  2475. }
  2476. }
  2477. // mockClient implements connect.Client interface for testing.
  2478. type mockClient struct {
  2479. getItemsFunc func(vaultQuery string) ([]onepassword.Item, error)
  2480. getVaultFunc func(vaultQuery string) (*onepassword.Vault, error)
  2481. }
  2482. func (m *mockClient) GetVaults() ([]onepassword.Vault, error) { return nil, nil }
  2483. func (m *mockClient) GetVault(uuid string) (*onepassword.Vault, error) {
  2484. if m.getVaultFunc != nil {
  2485. return m.getVaultFunc(uuid)
  2486. }
  2487. return nil, nil
  2488. }
  2489. func (m *mockClient) GetVaultByUUID(uuid string) (*onepassword.Vault, error) { return nil, nil }
  2490. func (m *mockClient) GetVaultByTitle(title string) (*onepassword.Vault, error) { return nil, nil }
  2491. func (m *mockClient) GetVaultsByTitle(uuid string) ([]onepassword.Vault, error) { return nil, nil }
  2492. func (m *mockClient) GetItems(vaultQuery string) ([]onepassword.Item, error) {
  2493. if m.getItemsFunc != nil {
  2494. return m.getItemsFunc(vaultQuery)
  2495. }
  2496. return nil, nil
  2497. }
  2498. func (m *mockClient) GetItem(itemQuery, vaultQuery string) (*onepassword.Item, error) {
  2499. return nil, nil
  2500. }
  2501. func (m *mockClient) GetItemByUUID(uuid, vaultQuery string) (*onepassword.Item, error) {
  2502. return nil, nil
  2503. }
  2504. func (m *mockClient) GetItemByTitle(title, vaultQuery string) (*onepassword.Item, error) {
  2505. return nil, nil
  2506. }
  2507. func (m *mockClient) GetItemsByTitle(title, vaultQuery string) ([]onepassword.Item, error) {
  2508. return nil, nil
  2509. }
  2510. func (m *mockClient) CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
  2511. return nil, nil
  2512. }
  2513. func (m *mockClient) UpdateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
  2514. return nil, nil
  2515. }
  2516. func (m *mockClient) DeleteItem(item *onepassword.Item, vaultQuery string) error { return nil }
  2517. func (m *mockClient) DeleteItemByID(itemUUID, vaultQuery string) error { return nil }
  2518. func (m *mockClient) DeleteItemByTitle(title, vaultQuery string) error { return nil }
  2519. func (m *mockClient) GetFiles(itemQuery, vaultQuery string) ([]onepassword.File, error) {
  2520. return nil, nil
  2521. }
  2522. func (m *mockClient) GetFile(uuid, itemQuery, vaultQuery string) (*onepassword.File, error) {
  2523. return nil, nil
  2524. }
  2525. func (m *mockClient) GetFileContent(file *onepassword.File) ([]byte, error) { return nil, nil }
  2526. func (m *mockClient) DownloadFile(file *onepassword.File, targetDirectory string, overwrite bool) (string, error) {
  2527. return "", nil
  2528. }
  2529. func (m *mockClient) LoadStructFromItemByUUID(config any, itemUUID, vaultQuery string) error {
  2530. return nil
  2531. }
  2532. func (m *mockClient) LoadStructFromItemByTitle(config any, itemTitle, vaultQuery string) error {
  2533. return nil
  2534. }
  2535. func (m *mockClient) LoadStructFromItem(config any, itemQuery, vaultQuery string) error {
  2536. return nil
  2537. }
  2538. func (m *mockClient) LoadStruct(config any) error { return nil }
  2539. func TestDeleteSecretWithEmptySections(t *testing.T) {
  2540. const vaultName = "vault1"
  2541. vault := onepassword.Vault{
  2542. ID: vaultName,
  2543. Name: vaultName,
  2544. }
  2545. t.Run("item with empty section should be deleted when last field is removed", func(t *testing.T) {
  2546. deleteCalled := false
  2547. updateCalled := false
  2548. mockClient := fake.NewMockClient()
  2549. mockClient.MockVaults = map[string][]onepassword.Vault{
  2550. vaultName: {vault},
  2551. }
  2552. mockClient.MockItems = map[string][]onepassword.Item{
  2553. vaultName: {
  2554. {
  2555. ID: "item-id",
  2556. Title: "test-item",
  2557. Vault: onepassword.ItemVault{ID: vaultName},
  2558. Sections: []*onepassword.ItemSection{
  2559. {ID: "", Label: ""},
  2560. },
  2561. },
  2562. },
  2563. }
  2564. mockClient.MockItemFields = map[string]map[string][]*onepassword.ItemField{
  2565. vaultName: {
  2566. "item-id": {
  2567. {ID: "field-1", Label: "password", Value: "secret"},
  2568. },
  2569. },
  2570. }
  2571. mockClient.DeleteItemValidateFunc = func(item *onepassword.Item, s string) error {
  2572. deleteCalled = true
  2573. return nil
  2574. }
  2575. mockClient.UpdateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2576. updateCalled = true
  2577. return item, nil
  2578. }
  2579. provider := &ProviderOnePassword{
  2580. vaults: map[string]int{vaultName: 1},
  2581. client: mockClient,
  2582. }
  2583. err := provider.DeleteSecret(context.Background(), fakeRef{
  2584. key: "test-item",
  2585. prop: "password",
  2586. })
  2587. if err != nil {
  2588. t.Errorf("expected no error, got %v", err)
  2589. }
  2590. if !deleteCalled {
  2591. t.Error("expected DeleteItem to be called when item has no fields and only empty sections")
  2592. }
  2593. if updateCalled {
  2594. t.Error("expected UpdateItem not to be called")
  2595. }
  2596. })
  2597. t.Run("item not found should not error", func(t *testing.T) {
  2598. mockClient := fake.NewMockClient()
  2599. mockClient.MockVaults = map[string][]onepassword.Vault{
  2600. vaultName: {vault},
  2601. }
  2602. mockClient.MockItems = map[string][]onepassword.Item{
  2603. vaultName: {},
  2604. }
  2605. provider := &ProviderOnePassword{
  2606. vaults: map[string]int{vaultName: 1},
  2607. client: mockClient,
  2608. }
  2609. err := provider.DeleteSecret(context.Background(), fakeRef{
  2610. key: "non-existent-item",
  2611. prop: "password",
  2612. })
  2613. if err != nil {
  2614. t.Errorf("expected no error when item not found, got %v", err)
  2615. }
  2616. })
  2617. }
  2618. func TestRetryClient(t *testing.T) {
  2619. tests := []struct {
  2620. name string
  2621. err error
  2622. shouldRetry bool
  2623. expectErr bool
  2624. }{
  2625. {
  2626. name: "403 auth error should retry",
  2627. err: errors.New("status 403: Authorization failed"),
  2628. shouldRetry: true,
  2629. expectErr: true,
  2630. },
  2631. {
  2632. name: "other error should not retry",
  2633. err: errors.New("status 500: Internal Server Error"),
  2634. shouldRetry: false,
  2635. expectErr: true,
  2636. },
  2637. {
  2638. name: "nil error should not retry",
  2639. err: nil,
  2640. shouldRetry: false,
  2641. expectErr: false,
  2642. },
  2643. }
  2644. for _, tc := range tests {
  2645. t.Run(tc.name, func(t *testing.T) {
  2646. callCount := 0
  2647. mockClient := &mockClient{
  2648. getItemsFunc: func(vaultQuery string) ([]onepassword.Item, error) {
  2649. callCount++
  2650. return nil, tc.err
  2651. },
  2652. }
  2653. retryClient := newRetryClient(mockClient)
  2654. _, err := retryClient.GetItems("test-vault")
  2655. if tc.expectErr && err == nil {
  2656. t.Errorf("expected error but got none")
  2657. }
  2658. if !tc.expectErr && err != nil {
  2659. t.Errorf("expected no error but got: %v", err)
  2660. }
  2661. expectedCalls := 1
  2662. if tc.shouldRetry {
  2663. expectedCalls = 3 // Initial call + 2 retries (3 steps configured in retry backoff)
  2664. }
  2665. if callCount < expectedCalls {
  2666. t.Errorf("expected at least %d calls but got %d", expectedCalls, callCount)
  2667. }
  2668. })
  2669. }
  2670. }
  2671. func TestIs403AuthError(t *testing.T) {
  2672. tests := []struct {
  2673. name string
  2674. err error
  2675. expected bool
  2676. }{
  2677. {
  2678. name: "nil error",
  2679. err: nil,
  2680. expected: false,
  2681. },
  2682. {
  2683. name: "403 auth error",
  2684. err: errors.New("status 403: Authorization failed"),
  2685. expected: true,
  2686. },
  2687. {
  2688. name: "other error",
  2689. err: errors.New("status 500: Internal Server Error"),
  2690. expected: false,
  2691. },
  2692. {
  2693. name: "partial match",
  2694. err: errors.New("403: some other message"),
  2695. expected: false,
  2696. },
  2697. }
  2698. for _, tc := range tests {
  2699. t.Run(tc.name, func(t *testing.T) {
  2700. result := is403AuthError(tc.err)
  2701. if result != tc.expected {
  2702. t.Errorf("expected %v but got %v", tc.expected, result)
  2703. }
  2704. })
  2705. }
  2706. }