auth_test.go 88 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793
  1. package foodgroup
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "log/slog"
  9. "testing"
  10. "time"
  11. "github.com/mk6i/open-oscar-server/config"
  12. "github.com/mk6i/open-oscar-server/state"
  13. "github.com/mk6i/open-oscar-server/wire"
  14. "github.com/google/uuid"
  15. "github.com/stretchr/testify/assert"
  16. "github.com/stretchr/testify/mock"
  17. )
  18. func TestAuthService_BUCPLoginRequest(t *testing.T) {
  19. user := state.User{
  20. IdentScreenName: state.NewIdentScreenName("screenName"),
  21. DisplayScreenName: "screenName",
  22. AuthKey: "auth_key",
  23. }
  24. assert.NoError(t, user.HashPassword("the_password"))
  25. cases := []struct {
  26. // name is the unit test name
  27. name string
  28. // endpointCfg is the listener the client authenticated through
  29. endpointCfg config.Endpoint
  30. // cfg is the app configuration
  31. cfg config.Config
  32. // inputSNAC is the SNAC sent from the client to the server
  33. inputSNAC wire.SNAC_0x17_0x02_BUCPLoginRequest
  34. // mockParams is the list of params sent to mocks that satisfy this
  35. // method's dependencies
  36. mockParams mockParams
  37. // createAccount is the function that creates a new user account
  38. createAccount state.CreateAccountFunc
  39. // expectOutput is the SNAC sent from the server to client
  40. expectOutput wire.SNACMessage
  41. // wantErr is the error we expect from the method
  42. wantErr error
  43. // maxConcurrentLoginsPerUser is the maximum concurrent logins per user (only set for MultiConnFlagsRecentClient tests)
  44. maxConcurrentLoginsPerUser int
  45. }{
  46. {
  47. name: "AIM account exists, correct password, login OK, no concurrent logins",
  48. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  49. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  50. TLVRestBlock: wire.TLVRestBlock{
  51. TLVList: wire.TLVList{
  52. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  53. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  54. wire.NewTLVBE(wire.LoginTLVTagsMultiConnFlags, wire.MultiConnFlagsRecentClient),
  55. },
  56. },
  57. },
  58. mockParams: mockParams{
  59. userManagerParams: userManagerParams{
  60. getUserParams: getUserParams{
  61. {
  62. screenName: user.IdentScreenName,
  63. result: &user,
  64. },
  65. },
  66. },
  67. cookieBakerParams: cookieBakerParams{
  68. cookieIssueParams: cookieIssueParams{
  69. {
  70. ttlIn: state.DefaultCookieTTL,
  71. dataIn: func() []byte {
  72. loginCookie := state.ServerCookie{
  73. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  74. ScreenName: user.DisplayScreenName,
  75. MultiConnFlag: uint8(wire.MultiConnFlagsRecentClient),
  76. }
  77. buf := &bytes.Buffer{}
  78. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  79. return buf.Bytes()
  80. }(),
  81. cookieOut: []byte("the-cookie"),
  82. },
  83. },
  84. },
  85. sessionRetrieverParams: sessionRetrieverParams{
  86. retrieveSessionParams: retrieveSessionParams{
  87. {
  88. screenName: user.IdentScreenName,
  89. result: nil,
  90. },
  91. },
  92. },
  93. },
  94. expectOutput: wire.SNACMessage{
  95. Frame: wire.SNACFrame{
  96. FoodGroup: wire.BUCP,
  97. SubGroup: wire.BUCPLoginResponse,
  98. },
  99. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  100. TLVRestBlock: wire.TLVRestBlock{
  101. TLVList: wire.TLVList{
  102. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  103. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  104. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  105. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  106. },
  107. },
  108. },
  109. },
  110. maxConcurrentLoginsPerUser: 2,
  111. },
  112. {
  113. name: "client requests a longer token TTL",
  114. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  115. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  116. TLVRestBlock: wire.TLVRestBlock{
  117. TLVList: wire.TLVList{
  118. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  119. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  120. wire.NewTLVBE(wire.LoginTLVTagsTokenTTL, uint32(3600)),
  121. },
  122. },
  123. },
  124. mockParams: mockParams{
  125. userManagerParams: userManagerParams{
  126. getUserParams: getUserParams{
  127. {
  128. screenName: user.IdentScreenName,
  129. result: &user,
  130. },
  131. },
  132. },
  133. cookieBakerParams: cookieBakerParams{
  134. cookieIssueParams: cookieIssueParams{
  135. {
  136. ttlIn: time.Hour,
  137. dataIn: func() []byte {
  138. loginCookie := state.ServerCookie{
  139. TokenTTL: uint32((time.Hour).Seconds()),
  140. ScreenName: user.DisplayScreenName,
  141. }
  142. buf := &bytes.Buffer{}
  143. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  144. return buf.Bytes()
  145. }(),
  146. cookieOut: []byte("the-cookie"),
  147. },
  148. },
  149. },
  150. },
  151. expectOutput: wire.SNACMessage{
  152. Frame: wire.SNACFrame{
  153. FoodGroup: wire.BUCP,
  154. SubGroup: wire.BUCPLoginResponse,
  155. },
  156. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  157. TLVRestBlock: wire.TLVRestBlock{
  158. TLVList: wire.TLVList{
  159. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  160. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  161. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  162. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  163. },
  164. },
  165. },
  166. },
  167. },
  168. {
  169. name: "AIM account exists, correct password, login OK, concurrent logins under limit",
  170. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  171. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  172. TLVRestBlock: wire.TLVRestBlock{
  173. TLVList: wire.TLVList{
  174. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  175. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  176. wire.NewTLVBE(wire.LoginTLVTagsMultiConnFlags, wire.MultiConnFlagsRecentClient),
  177. },
  178. },
  179. },
  180. mockParams: mockParams{
  181. userManagerParams: userManagerParams{
  182. getUserParams: getUserParams{
  183. {
  184. screenName: user.IdentScreenName,
  185. result: &user,
  186. },
  187. },
  188. },
  189. cookieBakerParams: cookieBakerParams{
  190. cookieIssueParams: cookieIssueParams{
  191. {
  192. ttlIn: state.DefaultCookieTTL,
  193. dataIn: func() []byte {
  194. loginCookie := state.ServerCookie{
  195. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  196. ScreenName: user.DisplayScreenName,
  197. MultiConnFlag: uint8(wire.MultiConnFlagsRecentClient),
  198. }
  199. buf := &bytes.Buffer{}
  200. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  201. return buf.Bytes()
  202. }(),
  203. cookieOut: []byte("the-cookie"),
  204. },
  205. },
  206. },
  207. sessionRetrieverParams: sessionRetrieverParams{
  208. retrieveSessionParams: retrieveSessionParams{
  209. {
  210. screenName: user.IdentScreenName,
  211. result: func() *state.Session {
  212. // Create a session with 1 instance, under the limit
  213. sess := state.NewSession()
  214. sess.SetIdentScreenName(user.IdentScreenName)
  215. sess.SetDisplayScreenName(user.DisplayScreenName)
  216. sess.AddInstance()
  217. return sess
  218. }(),
  219. },
  220. },
  221. },
  222. },
  223. expectOutput: wire.SNACMessage{
  224. Frame: wire.SNACFrame{
  225. FoodGroup: wire.BUCP,
  226. SubGroup: wire.BUCPLoginResponse,
  227. },
  228. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  229. TLVRestBlock: wire.TLVRestBlock{
  230. TLVList: wire.TLVList{
  231. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  232. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  233. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  234. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  235. },
  236. },
  237. },
  238. },
  239. maxConcurrentLoginsPerUser: 2,
  240. },
  241. {
  242. name: "login fails when concurrent login limit is reached",
  243. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  244. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  245. TLVRestBlock: wire.TLVRestBlock{
  246. TLVList: wire.TLVList{
  247. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  248. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  249. wire.NewTLVBE(wire.LoginTLVTagsMultiConnFlags, wire.MultiConnFlagsRecentClient),
  250. },
  251. },
  252. },
  253. mockParams: mockParams{
  254. userManagerParams: userManagerParams{
  255. getUserParams: getUserParams{
  256. {
  257. screenName: user.IdentScreenName,
  258. result: &user,
  259. },
  260. },
  261. },
  262. sessionRetrieverParams: sessionRetrieverParams{
  263. retrieveSessionParams: retrieveSessionParams{
  264. {
  265. screenName: user.IdentScreenName,
  266. result: func() *state.Session {
  267. // Create a session with 2 instances (the max allowed)
  268. // This will cause InstanceCount() to return 2, which equals the limit of 2
  269. sess := state.NewSession()
  270. sess.SetIdentScreenName(user.IdentScreenName)
  271. sess.SetDisplayScreenName(user.DisplayScreenName)
  272. sess.AddInstance()
  273. sess.AddInstance()
  274. return sess
  275. }(),
  276. },
  277. },
  278. },
  279. },
  280. expectOutput: wire.SNACMessage{
  281. Frame: wire.SNACFrame{
  282. FoodGroup: wire.BUCP,
  283. SubGroup: wire.BUCPLoginResponse,
  284. },
  285. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  286. TLVRestBlock: wire.TLVRestBlock{
  287. TLVList: []wire.TLV{
  288. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  289. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrRateLimitExceeded),
  290. },
  291. },
  292. },
  293. },
  294. maxConcurrentLoginsPerUser: 2,
  295. },
  296. {
  297. name: "ICQ account exists, correct password, login OK",
  298. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  299. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  300. TLVRestBlock: wire.TLVRestBlock{
  301. TLVList: wire.TLVList{
  302. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  303. wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"),
  304. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  305. },
  306. },
  307. },
  308. mockParams: mockParams{
  309. userManagerParams: userManagerParams{
  310. getUserParams: getUserParams{
  311. {
  312. screenName: user.IdentScreenName,
  313. result: &user,
  314. },
  315. },
  316. },
  317. cookieBakerParams: cookieBakerParams{
  318. cookieIssueParams: cookieIssueParams{
  319. {
  320. ttlIn: state.DefaultCookieTTL,
  321. dataIn: func() []byte {
  322. loginCookie := state.ServerCookie{
  323. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  324. ScreenName: user.DisplayScreenName,
  325. ClientID: "ICQ 2000b",
  326. }
  327. buf := &bytes.Buffer{}
  328. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  329. return buf.Bytes()
  330. }(),
  331. cookieOut: []byte("the-cookie"),
  332. },
  333. },
  334. },
  335. },
  336. expectOutput: wire.SNACMessage{
  337. Frame: wire.SNACFrame{
  338. FoodGroup: wire.BUCP,
  339. SubGroup: wire.BUCPLoginResponse,
  340. },
  341. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  342. TLVRestBlock: wire.TLVRestBlock{
  343. TLVList: wire.TLVList{
  344. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  345. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  346. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  347. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  348. },
  349. },
  350. },
  351. },
  352. },
  353. {
  354. name: "AIM account exists, incorrect password, login fails",
  355. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  356. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  357. TLVRestBlock: wire.TLVRestBlock{
  358. TLVList: wire.TLVList{
  359. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  360. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, []byte("bad_password")),
  361. },
  362. },
  363. },
  364. mockParams: mockParams{
  365. userManagerParams: userManagerParams{
  366. getUserParams: getUserParams{
  367. {
  368. screenName: user.IdentScreenName,
  369. result: &user,
  370. },
  371. },
  372. },
  373. },
  374. expectOutput: wire.SNACMessage{
  375. Frame: wire.SNACFrame{
  376. FoodGroup: wire.BUCP,
  377. SubGroup: wire.BUCPLoginResponse,
  378. },
  379. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  380. TLVRestBlock: wire.TLVRestBlock{
  381. TLVList: []wire.TLV{
  382. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  383. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
  384. },
  385. },
  386. },
  387. },
  388. },
  389. {
  390. name: "AIM account doesn't exist, login fails",
  391. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  392. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  393. TLVRestBlock: wire.TLVRestBlock{
  394. TLVList: wire.TLVList{
  395. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, []byte("password")),
  396. wire.NewTLVBE(wire.LoginTLVTagsScreenName, []byte("non_existent_screen_name")),
  397. },
  398. },
  399. },
  400. mockParams: mockParams{
  401. userManagerParams: userManagerParams{
  402. getUserParams: getUserParams{
  403. {
  404. screenName: state.NewIdentScreenName("non_existent_screen_name"),
  405. result: nil,
  406. },
  407. },
  408. },
  409. },
  410. expectOutput: wire.SNACMessage{
  411. Frame: wire.SNACFrame{
  412. FoodGroup: wire.BUCP,
  413. SubGroup: wire.BUCPLoginResponse,
  414. },
  415. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  416. TLVRestBlock: wire.TLVRestBlock{
  417. TLVList: []wire.TLV{
  418. wire.NewTLVBE(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("non_existent_screen_name")),
  419. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword),
  420. },
  421. },
  422. },
  423. },
  424. },
  425. {
  426. name: "AIM account is suspended",
  427. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  428. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  429. TLVRestBlock: wire.TLVRestBlock{
  430. TLVList: wire.TLVList{
  431. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, []byte("password")),
  432. wire.NewTLVBE(wire.LoginTLVTagsScreenName, []byte("suspended_screen_name")),
  433. },
  434. },
  435. },
  436. mockParams: mockParams{
  437. userManagerParams: userManagerParams{
  438. getUserParams: getUserParams{
  439. {
  440. screenName: state.NewIdentScreenName("suspended_screen_name"),
  441. result: &state.User{
  442. SuspendedStatus: wire.LoginErrSuspendedAccount,
  443. },
  444. },
  445. },
  446. },
  447. },
  448. expectOutput: wire.SNACMessage{
  449. Frame: wire.SNACFrame{
  450. FoodGroup: wire.BUCP,
  451. SubGroup: wire.BUCPLoginResponse,
  452. },
  453. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  454. TLVRestBlock: wire.TLVRestBlock{
  455. TLVList: []wire.TLV{
  456. wire.NewTLVBE(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("suspended_screen_name")),
  457. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrSuspendedAccount),
  458. },
  459. },
  460. },
  461. },
  462. },
  463. {
  464. name: "ICQ account doesn't exist, login fails",
  465. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  466. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  467. TLVRestBlock: wire.TLVRestBlock{
  468. TLVList: wire.TLVList{
  469. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, []byte("password")),
  470. wire.NewTLVBE(wire.LoginTLVTagsScreenName, []byte("100003")),
  471. },
  472. },
  473. },
  474. mockParams: mockParams{
  475. userManagerParams: userManagerParams{
  476. getUserParams: getUserParams{
  477. {
  478. screenName: state.NewIdentScreenName("100003"),
  479. result: nil,
  480. },
  481. },
  482. },
  483. },
  484. expectOutput: wire.SNACMessage{
  485. Frame: wire.SNACFrame{
  486. FoodGroup: wire.BUCP,
  487. SubGroup: wire.BUCPLoginResponse,
  488. },
  489. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  490. TLVRestBlock: wire.TLVRestBlock{
  491. TLVList: []wire.TLV{
  492. wire.NewTLVBE(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("100003")),
  493. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrICQUserErr),
  494. },
  495. },
  496. },
  497. },
  498. },
  499. {
  500. name: "account doesn't exist, authentication is disabled, account is created, login succeeds",
  501. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  502. cfg: config.Config{
  503. DisableAuth: true,
  504. },
  505. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  506. TLVRestBlock: wire.TLVRestBlock{
  507. TLVList: wire.TLVList{
  508. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  509. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  510. },
  511. },
  512. },
  513. mockParams: mockParams{
  514. userManagerParams: userManagerParams{
  515. getUserParams: getUserParams{
  516. {
  517. screenName: user.IdentScreenName,
  518. result: nil,
  519. },
  520. },
  521. },
  522. cookieBakerParams: cookieBakerParams{
  523. cookieIssueParams: cookieIssueParams{
  524. {
  525. ttlIn: state.DefaultCookieTTL,
  526. dataIn: func() []byte {
  527. loginCookie := state.ServerCookie{
  528. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  529. ScreenName: user.DisplayScreenName,
  530. }
  531. buf := &bytes.Buffer{}
  532. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  533. return buf.Bytes()
  534. }(),
  535. cookieOut: []byte("the-cookie"),
  536. },
  537. },
  538. },
  539. },
  540. createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
  541. assert.Equal(t, user.DisplayScreenName, screenName)
  542. assert.Equal(t, "welcome1", password)
  543. return nil
  544. },
  545. expectOutput: wire.SNACMessage{
  546. Frame: wire.SNACFrame{
  547. FoodGroup: wire.BUCP,
  548. SubGroup: wire.BUCPLoginResponse,
  549. },
  550. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  551. TLVRestBlock: wire.TLVRestBlock{
  552. TLVList: wire.TLVList{
  553. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  554. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  555. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  556. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  557. },
  558. },
  559. },
  560. },
  561. },
  562. {
  563. name: "AIM account doesn't exist, authentication is disabled, screen name has bad format, login fails",
  564. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  565. cfg: config.Config{
  566. DisableAuth: true,
  567. },
  568. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  569. TLVRestBlock: wire.TLVRestBlock{
  570. TLVList: wire.TLVList{
  571. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "2coolforschool"),
  572. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  573. },
  574. },
  575. },
  576. mockParams: mockParams{
  577. userManagerParams: userManagerParams{
  578. getUserParams: getUserParams{
  579. {
  580. screenName: state.NewIdentScreenName("2coolforschool"),
  581. result: nil,
  582. },
  583. },
  584. },
  585. },
  586. createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
  587. assert.Equal(t, state.DisplayScreenName("2coolforschool"), screenName)
  588. assert.Equal(t, "welcome1", password)
  589. return state.ErrAIMHandleInvalidFormat
  590. },
  591. expectOutput: wire.SNACMessage{
  592. Frame: wire.SNACFrame{
  593. FoodGroup: wire.BUCP,
  594. SubGroup: wire.BUCPLoginResponse,
  595. },
  596. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  597. TLVRestBlock: wire.TLVRestBlock{
  598. TLVList: wire.TLVList{
  599. wire.NewTLVBE(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("2coolforschool")),
  600. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword),
  601. },
  602. },
  603. },
  604. },
  605. },
  606. {
  607. name: "ICQ account doesn't exist, authentication is disabled, UIN has bad format, login fails",
  608. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  609. cfg: config.Config{
  610. DisableAuth: true,
  611. },
  612. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  613. TLVRestBlock: wire.TLVRestBlock{
  614. TLVList: wire.TLVList{
  615. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "99"),
  616. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  617. },
  618. },
  619. },
  620. mockParams: mockParams{
  621. userManagerParams: userManagerParams{
  622. getUserParams: getUserParams{
  623. {
  624. screenName: state.NewIdentScreenName("99"),
  625. result: nil,
  626. },
  627. },
  628. },
  629. },
  630. createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
  631. assert.Equal(t, state.DisplayScreenName("99"), screenName)
  632. assert.Equal(t, "welcome1", password)
  633. return state.ErrICQUINInvalidFormat
  634. },
  635. expectOutput: wire.SNACMessage{
  636. Frame: wire.SNACFrame{
  637. FoodGroup: wire.BUCP,
  638. SubGroup: wire.BUCPLoginResponse,
  639. },
  640. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  641. TLVRestBlock: wire.TLVRestBlock{
  642. TLVList: wire.TLVList{
  643. wire.NewTLVBE(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("99")),
  644. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrICQUserErr),
  645. },
  646. },
  647. },
  648. },
  649. },
  650. {
  651. name: "account exists, password is invalid, authentication is disabled, login succeeds",
  652. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  653. cfg: config.Config{
  654. DisableAuth: true,
  655. },
  656. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  657. TLVRestBlock: wire.TLVRestBlock{
  658. TLVList: wire.TLVList{
  659. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  660. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, []byte("bad-password-hash")),
  661. },
  662. },
  663. },
  664. mockParams: mockParams{
  665. userManagerParams: userManagerParams{
  666. getUserParams: getUserParams{
  667. {
  668. screenName: user.IdentScreenName,
  669. result: &user,
  670. },
  671. },
  672. },
  673. cookieBakerParams: cookieBakerParams{
  674. cookieIssueParams: cookieIssueParams{
  675. {
  676. ttlIn: state.DefaultCookieTTL,
  677. dataIn: func() []byte {
  678. loginCookie := state.ServerCookie{
  679. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  680. ScreenName: user.DisplayScreenName,
  681. }
  682. buf := &bytes.Buffer{}
  683. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  684. return buf.Bytes()
  685. }(),
  686. cookieOut: []byte("the-cookie"),
  687. },
  688. },
  689. },
  690. },
  691. expectOutput: wire.SNACMessage{
  692. Frame: wire.SNACFrame{
  693. FoodGroup: wire.BUCP,
  694. SubGroup: wire.BUCPLoginResponse,
  695. },
  696. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  697. TLVRestBlock: wire.TLVRestBlock{
  698. TLVList: wire.TLVList{
  699. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  700. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  701. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  702. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  703. },
  704. },
  705. },
  706. },
  707. },
  708. {
  709. name: "login fails on user manager lookup",
  710. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  711. TLVRestBlock: wire.TLVRestBlock{
  712. TLVList: wire.TLVList{
  713. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  714. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  715. },
  716. },
  717. },
  718. mockParams: mockParams{
  719. userManagerParams: userManagerParams{
  720. getUserParams: getUserParams{
  721. {
  722. screenName: user.IdentScreenName,
  723. err: io.EOF,
  724. },
  725. },
  726. },
  727. },
  728. wantErr: io.EOF,
  729. },
  730. {
  731. name: "login with TOC client - success",
  732. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  733. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  734. TLVRestBlock: wire.TLVRestBlock{
  735. TLVList: wire.TLVList{
  736. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  737. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, wire.RoastTOCPassword([]byte("the_password"))),
  738. },
  739. },
  740. },
  741. mockParams: mockParams{
  742. userManagerParams: userManagerParams{
  743. getUserParams: getUserParams{
  744. {
  745. screenName: user.IdentScreenName,
  746. result: &user,
  747. },
  748. },
  749. },
  750. cookieBakerParams: cookieBakerParams{
  751. cookieIssueParams: cookieIssueParams{
  752. {
  753. ttlIn: state.DefaultCookieTTL,
  754. dataIn: func() []byte {
  755. loginCookie := state.ServerCookie{
  756. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  757. ScreenName: user.DisplayScreenName,
  758. }
  759. buf := &bytes.Buffer{}
  760. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  761. return buf.Bytes()
  762. }(),
  763. cookieOut: []byte("the-cookie"),
  764. },
  765. },
  766. },
  767. },
  768. expectOutput: wire.SNACMessage{
  769. Frame: wire.SNACFrame{
  770. FoodGroup: wire.BUCP,
  771. SubGroup: wire.BUCPLoginResponse,
  772. },
  773. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  774. TLVRestBlock: wire.TLVRestBlock{
  775. TLVList: wire.TLVList{
  776. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  777. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  778. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  779. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  780. },
  781. },
  782. },
  783. },
  784. },
  785. {
  786. name: "AIM account exists, correct password, linked accounts in response",
  787. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  788. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  789. TLVRestBlock: wire.TLVRestBlock{
  790. TLVList: wire.TLVList{
  791. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  792. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  793. },
  794. },
  795. },
  796. mockParams: mockParams{
  797. userManagerParams: userManagerParams{
  798. getUserParams: getUserParams{
  799. {
  800. screenName: user.IdentScreenName,
  801. result: &user,
  802. },
  803. },
  804. },
  805. cookieBakerParams: cookieBakerParams{
  806. cookieIssueParams: cookieIssueParams{
  807. {
  808. ttlIn: state.DefaultCookieTTL,
  809. dataIn: func() []byte {
  810. loginCookie := state.ServerCookie{
  811. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  812. ScreenName: user.DisplayScreenName,
  813. }
  814. buf := &bytes.Buffer{}
  815. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  816. return buf.Bytes()
  817. }(),
  818. cookieOut: []byte("the-cookie"),
  819. },
  820. },
  821. },
  822. feedbagManagerParams: feedbagManagerParams{
  823. feedbagParams: feedbagParams{
  824. {
  825. screenName: user.IdentScreenName,
  826. results: []wire.FeedbagItem{{ClassID: wire.FeedbagClassIdAlInfo, Name: "linked1"}},
  827. },
  828. },
  829. },
  830. },
  831. expectOutput: wire.SNACMessage{
  832. Frame: wire.SNACFrame{
  833. FoodGroup: wire.BUCP,
  834. SubGroup: wire.BUCPLoginResponse,
  835. },
  836. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  837. TLVRestBlock: wire.TLVRestBlock{
  838. TLVList: wire.TLVList{
  839. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  840. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  841. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  842. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  843. wire.NewTLVBE(wire.OServiceTLVTagsLinkedAccounts, `<SET SETID="1"><RESREC TYPE="PRIMARY-ACCOUNT" ID="1"><n>screenname</n></RESREC><RESREC TYPE="LINKED-ACCOUNT" ID="2"><n>linked1</n></RESREC></SET>`),
  844. },
  845. },
  846. },
  847. },
  848. },
  849. {
  850. name: "feedbag error during login, returns error",
  851. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  852. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  853. TLVRestBlock: wire.TLVRestBlock{
  854. TLVList: wire.TLVList{
  855. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  856. wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass),
  857. },
  858. },
  859. },
  860. mockParams: mockParams{
  861. userManagerParams: userManagerParams{
  862. getUserParams: getUserParams{
  863. {
  864. screenName: user.IdentScreenName,
  865. result: &user,
  866. },
  867. },
  868. },
  869. cookieBakerParams: cookieBakerParams{
  870. cookieIssueParams: cookieIssueParams{
  871. {
  872. ttlIn: state.DefaultCookieTTL,
  873. dataIn: func() []byte {
  874. loginCookie := state.ServerCookie{
  875. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  876. ScreenName: user.DisplayScreenName,
  877. }
  878. buf := &bytes.Buffer{}
  879. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  880. return buf.Bytes()
  881. }(),
  882. cookieOut: []byte("the-cookie"),
  883. },
  884. },
  885. },
  886. feedbagManagerParams: feedbagManagerParams{
  887. feedbagParams: feedbagParams{
  888. {
  889. screenName: user.IdentScreenName,
  890. err: io.EOF,
  891. },
  892. },
  893. },
  894. },
  895. wantErr: io.EOF,
  896. },
  897. {
  898. name: "login with TOC client - failed",
  899. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  900. inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
  901. TLVRestBlock: wire.TLVRestBlock{
  902. TLVList: wire.TLVList{
  903. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  904. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, wire.RoastTOCPassword([]byte("the_wrong_password"))),
  905. },
  906. },
  907. },
  908. mockParams: mockParams{
  909. userManagerParams: userManagerParams{
  910. getUserParams: getUserParams{
  911. {
  912. screenName: user.IdentScreenName,
  913. result: &user,
  914. },
  915. },
  916. },
  917. },
  918. expectOutput: wire.SNACMessage{
  919. Frame: wire.SNACFrame{
  920. FoodGroup: wire.BUCP,
  921. SubGroup: wire.BUCPLoginResponse,
  922. },
  923. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  924. TLVRestBlock: wire.TLVRestBlock{
  925. TLVList: wire.TLVList{
  926. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "screenName"),
  927. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
  928. },
  929. },
  930. },
  931. },
  932. },
  933. }
  934. for _, tc := range cases {
  935. t.Run(tc.name, func(t *testing.T) {
  936. userManager := newMockUserManager(t)
  937. for _, params := range tc.mockParams.userManagerParams.getUserParams {
  938. userManager.EXPECT().
  939. User(matchContext(), params.screenName).
  940. Return(params.result, params.err)
  941. }
  942. cookieBaker := newMockCookieBaker(t)
  943. for _, params := range tc.mockParams.cookieIssueParams {
  944. cookieBaker.EXPECT().
  945. Issue(params.dataIn, params.ttlIn).
  946. Return(params.cookieOut, params.err)
  947. }
  948. sessionRetriever := newMockSessionRetriever(t)
  949. for _, params := range tc.mockParams.retrieveSessionParams {
  950. sessionRetriever.EXPECT().
  951. RetrieveSession(params.screenName).
  952. Return(params.result)
  953. }
  954. feedbagManager := newMockFeedbagManager(t)
  955. for _, params := range tc.mockParams.feedbagParams {
  956. feedbagManager.EXPECT().
  957. Feedbag(matchContext(), params.screenName).
  958. Return(params.results, params.err)
  959. }
  960. feedbagManager.EXPECT().Feedbag(matchContext(), mock.Anything).Return(nil, nil).Maybe()
  961. svc := AuthService{
  962. config: tc.cfg,
  963. cookieBaker: cookieBaker,
  964. userManager: userManager,
  965. sessionRetriever: sessionRetriever,
  966. feedbagManager: feedbagManager,
  967. maxConcurrentLoginsPerUser: 2,
  968. createAccount: tc.createAccount,
  969. logger: slog.Default(),
  970. }
  971. outputSNAC, err := svc.BUCPLogin(context.Background(), tc.inputSNAC, tc.endpointCfg)
  972. assert.ErrorIs(t, err, tc.wantErr)
  973. assert.Equal(t, tc.expectOutput, outputSNAC)
  974. })
  975. }
  976. }
  977. func TestAuthService_FLAPLogin(t *testing.T) {
  978. user := state.User{
  979. AuthKey: "auth_key",
  980. DisplayScreenName: "screenName",
  981. IdentScreenName: state.NewIdentScreenName("screenName"),
  982. }
  983. assert.NoError(t, user.HashPassword("the_password"))
  984. cases := []struct {
  985. // name is the unit test name
  986. name string
  987. // endpointCfg is the listener the client authenticated through
  988. endpointCfg config.Endpoint
  989. // cfg is the app configuration
  990. cfg config.Config
  991. // inputSNAC is the authentication FLAP frame sent from the client to the server
  992. inputSNAC wire.FLAPSignonFrame
  993. // mockParams is the list of params sent to mocks that satisfy this
  994. // method's dependencies
  995. mockParams mockParams
  996. // createAccount is the function that creates a new user account
  997. createAccount state.CreateAccountFunc
  998. // expectOutput is the response sent from the server to client
  999. expectOutput wire.TLVRestBlock
  1000. // wantErr is the error we expect from the method
  1001. wantErr error
  1002. }{
  1003. {
  1004. name: "AIM account exists, correct password, login OK",
  1005. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1006. inputSNAC: wire.FLAPSignonFrame{
  1007. TLVRestBlock: wire.TLVRestBlock{
  1008. TLVList: wire.TLVList{
  1009. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1010. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1011. },
  1012. },
  1013. },
  1014. mockParams: mockParams{
  1015. userManagerParams: userManagerParams{
  1016. getUserParams: getUserParams{
  1017. {
  1018. screenName: user.IdentScreenName,
  1019. result: &user,
  1020. },
  1021. },
  1022. },
  1023. cookieBakerParams: cookieBakerParams{
  1024. cookieIssueParams: cookieIssueParams{
  1025. {
  1026. ttlIn: state.DefaultCookieTTL,
  1027. dataIn: func() []byte {
  1028. loginCookie := state.ServerCookie{
  1029. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  1030. ScreenName: user.DisplayScreenName,
  1031. }
  1032. buf := &bytes.Buffer{}
  1033. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1034. return buf.Bytes()
  1035. }(),
  1036. cookieOut: []byte("the-cookie"),
  1037. },
  1038. },
  1039. },
  1040. },
  1041. expectOutput: wire.TLVRestBlock{
  1042. TLVList: wire.TLVList{
  1043. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1044. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  1045. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  1046. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  1047. },
  1048. },
  1049. },
  1050. {
  1051. name: "client requests a longer token TTL",
  1052. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1053. inputSNAC: wire.FLAPSignonFrame{
  1054. TLVRestBlock: wire.TLVRestBlock{
  1055. TLVList: wire.TLVList{
  1056. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1057. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1058. wire.NewTLVBE(wire.LoginTLVTagsTokenTTL, uint32(3600)),
  1059. },
  1060. },
  1061. },
  1062. mockParams: mockParams{
  1063. userManagerParams: userManagerParams{
  1064. getUserParams: getUserParams{
  1065. {
  1066. screenName: user.IdentScreenName,
  1067. result: &user,
  1068. },
  1069. },
  1070. },
  1071. cookieBakerParams: cookieBakerParams{
  1072. cookieIssueParams: cookieIssueParams{
  1073. {
  1074. ttlIn: time.Hour,
  1075. dataIn: func() []byte {
  1076. loginCookie := state.ServerCookie{
  1077. TokenTTL: uint32((time.Hour).Seconds()),
  1078. ScreenName: user.DisplayScreenName,
  1079. }
  1080. buf := &bytes.Buffer{}
  1081. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1082. return buf.Bytes()
  1083. }(),
  1084. cookieOut: []byte("the-cookie"),
  1085. },
  1086. },
  1087. },
  1088. },
  1089. expectOutput: wire.TLVRestBlock{
  1090. TLVList: wire.TLVList{
  1091. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1092. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  1093. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  1094. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  1095. },
  1096. },
  1097. },
  1098. {
  1099. name: "client requests exactly the max token TTL",
  1100. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1101. inputSNAC: wire.FLAPSignonFrame{
  1102. TLVRestBlock: wire.TLVRestBlock{
  1103. TLVList: wire.TLVList{
  1104. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1105. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1106. wire.NewTLVBE(wire.LoginTLVTagsTokenTTL, uint32(maxTokenTTL.Seconds())),
  1107. },
  1108. },
  1109. },
  1110. mockParams: mockParams{
  1111. userManagerParams: userManagerParams{
  1112. getUserParams: getUserParams{
  1113. {
  1114. screenName: user.IdentScreenName,
  1115. result: &user,
  1116. },
  1117. },
  1118. },
  1119. cookieBakerParams: cookieBakerParams{
  1120. cookieIssueParams: cookieIssueParams{
  1121. {
  1122. ttlIn: maxTokenTTL,
  1123. dataIn: func() []byte {
  1124. loginCookie := state.ServerCookie{
  1125. TokenTTL: uint32((maxTokenTTL).Seconds()),
  1126. ScreenName: user.DisplayScreenName,
  1127. }
  1128. buf := &bytes.Buffer{}
  1129. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1130. return buf.Bytes()
  1131. }(),
  1132. cookieOut: []byte("the-cookie"),
  1133. },
  1134. },
  1135. },
  1136. },
  1137. expectOutput: wire.TLVRestBlock{
  1138. TLVList: wire.TLVList{
  1139. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1140. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  1141. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  1142. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  1143. },
  1144. },
  1145. },
  1146. {
  1147. name: "client requests a zero token TTL, login fails",
  1148. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1149. inputSNAC: wire.FLAPSignonFrame{
  1150. TLVRestBlock: wire.TLVRestBlock{
  1151. TLVList: wire.TLVList{
  1152. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1153. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1154. wire.NewTLVBE(wire.LoginTLVTagsTokenTTL, uint32(0)),
  1155. },
  1156. },
  1157. },
  1158. expectOutput: wire.TLVRestBlock{
  1159. TLVList: []wire.TLV{
  1160. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1161. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInternalClientError),
  1162. },
  1163. },
  1164. },
  1165. {
  1166. name: "client requests a token TTL beyond the max, login fails",
  1167. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1168. inputSNAC: wire.FLAPSignonFrame{
  1169. TLVRestBlock: wire.TLVRestBlock{
  1170. TLVList: wire.TLVList{
  1171. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1172. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1173. wire.NewTLVBE(wire.LoginTLVTagsTokenTTL, uint32(maxTokenTTL.Seconds())+1),
  1174. },
  1175. },
  1176. },
  1177. expectOutput: wire.TLVRestBlock{
  1178. TLVList: []wire.TLV{
  1179. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1180. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInternalClientError),
  1181. },
  1182. },
  1183. },
  1184. {
  1185. name: "AIM account exists, correct password, login OK via SSL listener",
  1186. endpointCfg: config.Endpoint{
  1187. Group: config.ListenerGroup{
  1188. BOSAdvertisedHostPlain: "127.0.0.1:5190",
  1189. BOSAdvertisedHostSSL: "ras.dev:5193",
  1190. },
  1191. IsSSL: true,
  1192. },
  1193. inputSNAC: wire.FLAPSignonFrame{
  1194. TLVRestBlock: wire.TLVRestBlock{
  1195. TLVList: wire.TLVList{
  1196. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1197. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1198. },
  1199. },
  1200. },
  1201. mockParams: mockParams{
  1202. userManagerParams: userManagerParams{
  1203. getUserParams: getUserParams{
  1204. {
  1205. screenName: user.IdentScreenName,
  1206. result: &user,
  1207. },
  1208. },
  1209. },
  1210. cookieBakerParams: cookieBakerParams{
  1211. cookieIssueParams: cookieIssueParams{
  1212. {
  1213. ttlIn: state.DefaultCookieTTL,
  1214. dataIn: func() []byte {
  1215. loginCookie := state.ServerCookie{
  1216. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  1217. ScreenName: user.DisplayScreenName,
  1218. }
  1219. buf := &bytes.Buffer{}
  1220. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1221. return buf.Bytes()
  1222. }(),
  1223. cookieOut: []byte("the-cookie"),
  1224. },
  1225. },
  1226. },
  1227. },
  1228. expectOutput: wire.TLVRestBlock{
  1229. TLVList: wire.TLVList{
  1230. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1231. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "ras.dev:5193"),
  1232. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  1233. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, wire.OServiceServiceResponseSSLStateResume),
  1234. },
  1235. },
  1236. },
  1237. {
  1238. name: "ICQ account exists, correct password, login OK",
  1239. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1240. inputSNAC: wire.FLAPSignonFrame{
  1241. TLVRestBlock: wire.TLVRestBlock{
  1242. TLVList: wire.TLVList{
  1243. wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"),
  1244. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1245. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1246. },
  1247. },
  1248. },
  1249. mockParams: mockParams{
  1250. userManagerParams: userManagerParams{
  1251. getUserParams: getUserParams{
  1252. {
  1253. screenName: user.IdentScreenName,
  1254. result: &user,
  1255. },
  1256. },
  1257. },
  1258. cookieBakerParams: cookieBakerParams{
  1259. cookieIssueParams: cookieIssueParams{
  1260. {
  1261. ttlIn: state.DefaultCookieTTL,
  1262. dataIn: func() []byte {
  1263. loginCookie := state.ServerCookie{
  1264. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  1265. ScreenName: user.DisplayScreenName,
  1266. ClientID: "ICQ 2000b",
  1267. }
  1268. buf := &bytes.Buffer{}
  1269. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1270. return buf.Bytes()
  1271. }(),
  1272. cookieOut: []byte("the-cookie"),
  1273. },
  1274. },
  1275. },
  1276. },
  1277. expectOutput: wire.TLVRestBlock{
  1278. TLVList: wire.TLVList{
  1279. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1280. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  1281. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  1282. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  1283. },
  1284. },
  1285. },
  1286. {
  1287. name: "AIM account exists, incorrect password, login fails",
  1288. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1289. inputSNAC: wire.FLAPSignonFrame{
  1290. TLVRestBlock: wire.TLVRestBlock{
  1291. TLVList: wire.TLVList{
  1292. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, []byte("bad_roasted_password")),
  1293. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1294. },
  1295. },
  1296. },
  1297. mockParams: mockParams{
  1298. userManagerParams: userManagerParams{
  1299. getUserParams: getUserParams{
  1300. {
  1301. screenName: user.IdentScreenName,
  1302. result: &user,
  1303. },
  1304. },
  1305. },
  1306. },
  1307. expectOutput: wire.TLVRestBlock{
  1308. TLVList: []wire.TLV{
  1309. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1310. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
  1311. },
  1312. },
  1313. },
  1314. {
  1315. name: "AIM account doesn't exist, login fails",
  1316. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1317. inputSNAC: wire.FLAPSignonFrame{
  1318. TLVRestBlock: wire.TLVRestBlock{
  1319. TLVList: wire.TLVList{
  1320. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1321. wire.NewTLVBE(wire.LoginTLVTagsScreenName, []byte("non_existent_screen_name")),
  1322. },
  1323. },
  1324. },
  1325. mockParams: mockParams{
  1326. userManagerParams: userManagerParams{
  1327. getUserParams: getUserParams{
  1328. {
  1329. screenName: state.NewIdentScreenName("non_existent_screen_name"),
  1330. result: nil,
  1331. },
  1332. },
  1333. },
  1334. },
  1335. expectOutput: wire.TLVRestBlock{
  1336. TLVList: []wire.TLV{
  1337. wire.NewTLVBE(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("non_existent_screen_name")),
  1338. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword),
  1339. },
  1340. },
  1341. },
  1342. {
  1343. name: "ICQ account doesn't exist, login fails",
  1344. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1345. inputSNAC: wire.FLAPSignonFrame{
  1346. TLVRestBlock: wire.TLVRestBlock{
  1347. TLVList: wire.TLVList{
  1348. wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"),
  1349. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1350. wire.NewTLVBE(wire.LoginTLVTagsScreenName, []byte("100003")),
  1351. },
  1352. },
  1353. },
  1354. mockParams: mockParams{
  1355. userManagerParams: userManagerParams{
  1356. getUserParams: getUserParams{
  1357. {
  1358. screenName: state.NewIdentScreenName("100003"),
  1359. result: nil,
  1360. },
  1361. },
  1362. },
  1363. },
  1364. expectOutput: wire.TLVRestBlock{
  1365. TLVList: []wire.TLV{
  1366. wire.NewTLVBE(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("100003")),
  1367. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrICQUserErr),
  1368. },
  1369. },
  1370. },
  1371. {
  1372. name: "account doesn't exist, authentication is disabled, account is created, login succeeds",
  1373. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1374. cfg: config.Config{
  1375. DisableAuth: true,
  1376. },
  1377. inputSNAC: wire.FLAPSignonFrame{
  1378. TLVRestBlock: wire.TLVRestBlock{
  1379. TLVList: wire.TLVList{
  1380. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1381. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1382. },
  1383. },
  1384. },
  1385. mockParams: mockParams{
  1386. userManagerParams: userManagerParams{
  1387. getUserParams: getUserParams{
  1388. {
  1389. screenName: user.IdentScreenName,
  1390. result: nil,
  1391. },
  1392. },
  1393. },
  1394. cookieBakerParams: cookieBakerParams{
  1395. cookieIssueParams: cookieIssueParams{
  1396. {
  1397. ttlIn: state.DefaultCookieTTL,
  1398. dataIn: func() []byte {
  1399. loginCookie := state.ServerCookie{
  1400. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  1401. ScreenName: user.DisplayScreenName,
  1402. }
  1403. buf := &bytes.Buffer{}
  1404. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1405. return buf.Bytes()
  1406. }(),
  1407. cookieOut: []byte("the-cookie"),
  1408. },
  1409. },
  1410. },
  1411. },
  1412. createAccount: func(ctx context.Context, screenName state.DisplayScreenName, password string) error {
  1413. assert.Equal(t, user.DisplayScreenName, screenName)
  1414. assert.Equal(t, "welcome1", password)
  1415. return nil
  1416. },
  1417. expectOutput: wire.TLVRestBlock{
  1418. TLVList: wire.TLVList{
  1419. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1420. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  1421. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  1422. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  1423. },
  1424. },
  1425. },
  1426. {
  1427. name: "account exists, password is invalid, authentication is disabled, login succeeds",
  1428. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1429. cfg: config.Config{
  1430. DisableAuth: true,
  1431. },
  1432. inputSNAC: wire.FLAPSignonFrame{
  1433. TLVRestBlock: wire.TLVRestBlock{
  1434. TLVList: wire.TLVList{
  1435. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, "bad-roasted-password"),
  1436. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1437. },
  1438. },
  1439. },
  1440. mockParams: mockParams{
  1441. userManagerParams: userManagerParams{
  1442. getUserParams: getUserParams{
  1443. {
  1444. screenName: user.IdentScreenName,
  1445. result: &user,
  1446. },
  1447. },
  1448. },
  1449. cookieBakerParams: cookieBakerParams{
  1450. cookieIssueParams: cookieIssueParams{
  1451. {
  1452. ttlIn: state.DefaultCookieTTL,
  1453. dataIn: func() []byte {
  1454. loginCookie := state.ServerCookie{
  1455. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  1456. ScreenName: user.DisplayScreenName,
  1457. }
  1458. buf := &bytes.Buffer{}
  1459. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1460. return buf.Bytes()
  1461. }(),
  1462. cookieOut: []byte("the-cookie"),
  1463. },
  1464. },
  1465. },
  1466. },
  1467. expectOutput: wire.TLVRestBlock{
  1468. TLVList: wire.TLVList{
  1469. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1470. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  1471. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  1472. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  1473. },
  1474. },
  1475. },
  1476. {
  1477. name: "feedbag error during login, returns error",
  1478. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1479. inputSNAC: wire.FLAPSignonFrame{
  1480. TLVRestBlock: wire.TLVRestBlock{
  1481. TLVList: wire.TLVList{
  1482. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1483. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1484. },
  1485. },
  1486. },
  1487. mockParams: mockParams{
  1488. userManagerParams: userManagerParams{
  1489. getUserParams: getUserParams{
  1490. {
  1491. screenName: user.IdentScreenName,
  1492. result: &user,
  1493. },
  1494. },
  1495. },
  1496. cookieBakerParams: cookieBakerParams{
  1497. cookieIssueParams: cookieIssueParams{
  1498. {
  1499. ttlIn: state.DefaultCookieTTL,
  1500. dataIn: func() []byte {
  1501. loginCookie := state.ServerCookie{
  1502. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  1503. ScreenName: user.DisplayScreenName,
  1504. }
  1505. buf := &bytes.Buffer{}
  1506. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1507. return buf.Bytes()
  1508. }(),
  1509. cookieOut: []byte("the-cookie"),
  1510. },
  1511. },
  1512. },
  1513. feedbagManagerParams: feedbagManagerParams{
  1514. feedbagParams: feedbagParams{
  1515. {
  1516. screenName: user.IdentScreenName,
  1517. err: io.EOF,
  1518. },
  1519. },
  1520. },
  1521. },
  1522. wantErr: io.EOF,
  1523. },
  1524. {
  1525. name: "login fails on user manager lookup",
  1526. inputSNAC: wire.FLAPSignonFrame{
  1527. TLVRestBlock: wire.TLVRestBlock{
  1528. TLVList: wire.TLVList{
  1529. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
  1530. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1531. },
  1532. },
  1533. },
  1534. mockParams: mockParams{
  1535. userManagerParams: userManagerParams{
  1536. getUserParams: getUserParams{
  1537. {
  1538. screenName: user.IdentScreenName,
  1539. err: io.EOF,
  1540. },
  1541. },
  1542. },
  1543. },
  1544. wantErr: io.EOF,
  1545. },
  1546. {
  1547. name: "login with AIM 1.1.19 for Java - success",
  1548. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1549. inputSNAC: wire.FLAPSignonFrame{
  1550. TLVRestBlock: wire.TLVRestBlock{
  1551. TLVList: wire.TLVList{
  1552. wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "AOL Instant Messenger (TM) version 1.1.19 for Java"),
  1553. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1554. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARJavaPassword([]byte("the_password"))),
  1555. },
  1556. },
  1557. },
  1558. mockParams: mockParams{
  1559. userManagerParams: userManagerParams{
  1560. getUserParams: getUserParams{
  1561. {
  1562. screenName: user.IdentScreenName,
  1563. result: &user,
  1564. },
  1565. },
  1566. },
  1567. cookieBakerParams: cookieBakerParams{
  1568. cookieIssueParams: cookieIssueParams{
  1569. {
  1570. ttlIn: state.DefaultCookieTTL,
  1571. dataIn: func() []byte {
  1572. loginCookie := state.ServerCookie{
  1573. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  1574. ScreenName: user.DisplayScreenName,
  1575. ClientID: "AOL Instant Messenger (TM) version 1.1.19 for Java",
  1576. }
  1577. buf := &bytes.Buffer{}
  1578. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1579. return buf.Bytes()
  1580. }(),
  1581. cookieOut: []byte("the-cookie"),
  1582. },
  1583. },
  1584. },
  1585. },
  1586. expectOutput: wire.TLVRestBlock{
  1587. TLVList: wire.TLVList{
  1588. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1589. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:5190"),
  1590. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
  1591. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  1592. },
  1593. },
  1594. },
  1595. {
  1596. name: "login with AIM 1.1.19 for Java - failed",
  1597. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}},
  1598. inputSNAC: wire.FLAPSignonFrame{
  1599. TLVRestBlock: wire.TLVRestBlock{
  1600. TLVList: wire.TLVList{
  1601. wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "AOL Instant Messenger (TM) version 1.1.19 for Java"),
  1602. wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
  1603. wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARJavaPassword([]byte("the_wrong_password"))),
  1604. },
  1605. },
  1606. },
  1607. mockParams: mockParams{
  1608. userManagerParams: userManagerParams{
  1609. getUserParams: getUserParams{
  1610. {
  1611. screenName: user.IdentScreenName,
  1612. result: &user,
  1613. },
  1614. },
  1615. },
  1616. },
  1617. expectOutput: wire.TLVRestBlock{
  1618. TLVList: wire.TLVList{
  1619. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "screenName"),
  1620. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
  1621. },
  1622. },
  1623. },
  1624. }
  1625. for _, tc := range cases {
  1626. t.Run(tc.name, func(t *testing.T) {
  1627. userManager := newMockUserManager(t)
  1628. for _, params := range tc.mockParams.userManagerParams.getUserParams {
  1629. userManager.EXPECT().
  1630. User(matchContext(), params.screenName).
  1631. Return(params.result, params.err)
  1632. }
  1633. cookieBaker := newMockCookieBaker(t)
  1634. for _, params := range tc.mockParams.cookieIssueParams {
  1635. cookieBaker.EXPECT().
  1636. Issue(params.dataIn, params.ttlIn).
  1637. Return(params.cookieOut, params.err)
  1638. }
  1639. feedbagManager := newMockFeedbagManager(t)
  1640. for _, params := range tc.mockParams.feedbagParams {
  1641. feedbagManager.EXPECT().
  1642. Feedbag(matchContext(), params.screenName).
  1643. Return(params.results, params.err)
  1644. }
  1645. feedbagManager.EXPECT().Feedbag(matchContext(), mock.Anything).Return(nil, nil).Maybe()
  1646. sessionRetriever := newMockSessionRetriever(t)
  1647. for _, params := range tc.mockParams.retrieveSessionParams {
  1648. sessionRetriever.EXPECT().
  1649. RetrieveSession(params.screenName).
  1650. Return(params.result)
  1651. }
  1652. svc := AuthService{
  1653. config: tc.cfg,
  1654. cookieBaker: cookieBaker,
  1655. userManager: userManager,
  1656. feedbagManager: feedbagManager,
  1657. createAccount: tc.createAccount,
  1658. sessionRetriever: sessionRetriever,
  1659. maxConcurrentLoginsPerUser: 2,
  1660. logger: slog.Default(),
  1661. }
  1662. outputSNAC, err := svc.FLAPLogin(context.Background(), tc.inputSNAC, tc.endpointCfg)
  1663. assert.ErrorIs(t, err, tc.wantErr)
  1664. assert.Equal(t, tc.expectOutput, outputSNAC)
  1665. })
  1666. }
  1667. }
  1668. func TestAuthService_KerberosLogin(t *testing.T) {
  1669. user := state.User{
  1670. AuthKey: "auth_key",
  1671. DisplayScreenName: "screenName",
  1672. IdentScreenName: state.NewIdentScreenName("screenName"),
  1673. }
  1674. assert.NoError(t, user.HashPassword("the_password"))
  1675. cases := []struct {
  1676. // name is the unit test name
  1677. name string
  1678. // endpointCfg is the SSL listener the client authenticated through
  1679. endpointCfg config.Endpoint
  1680. // cfg is the app configuration
  1681. cfg config.Config
  1682. // inputSNAC is the kerberos SNAC sent from the client to the server
  1683. inputSNAC wire.SNAC_0x050C_0x0002_KerberosLoginRequest
  1684. // mockParams is the list of params sent to mocks that satisfy this
  1685. // method's dependencies
  1686. mockParams mockParams
  1687. // createAccount is the function that creates a new user account
  1688. createAccount state.CreateAccountFunc
  1689. // expectOutput is the response sent from the server to client
  1690. expectOutput wire.SNACMessage
  1691. // wantErr is the error we expect from the method
  1692. wantErr error
  1693. // timeNow returns a canned time value
  1694. timeNow func() time.Time
  1695. }{
  1696. {
  1697. name: "AIM account exists, correct password, login OK",
  1698. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostSSL: "127.0.0.1:5190"}, IsSSL: true},
  1699. timeNow: func() time.Time {
  1700. return time.Unix(1000, 0)
  1701. },
  1702. inputSNAC: wire.SNAC_0x050C_0x0002_KerberosLoginRequest{
  1703. RequestID: 54321,
  1704. ClientPrincipal: user.DisplayScreenName.String(),
  1705. TicketRequestMetadata: wire.TLVBlock{
  1706. TLVList: wire.TLVList{
  1707. wire.NewTLVBE(wire.KerberosTLVTicketRequest, wire.KerberosLoginRequestTicket{
  1708. Password: []byte("the_password"),
  1709. }),
  1710. },
  1711. },
  1712. },
  1713. mockParams: mockParams{
  1714. userManagerParams: userManagerParams{
  1715. getUserParams: getUserParams{
  1716. {
  1717. screenName: user.IdentScreenName,
  1718. result: &user,
  1719. },
  1720. },
  1721. },
  1722. cookieBakerParams: cookieBakerParams{
  1723. cookieIssueParams: cookieIssueParams{
  1724. {
  1725. ttlIn: state.DefaultCookieTTL,
  1726. dataIn: func() []byte {
  1727. loginCookie := state.ServerCookie{
  1728. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  1729. Service: wire.BOS,
  1730. ScreenName: user.DisplayScreenName,
  1731. ClientID: "",
  1732. MultiConnFlag: uint8(wire.MultiConnFlagsRecentClient),
  1733. KerberosAuth: 1,
  1734. }
  1735. buf := &bytes.Buffer{}
  1736. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1737. return buf.Bytes()
  1738. }(),
  1739. cookieOut: []byte("the-cookie"),
  1740. },
  1741. },
  1742. },
  1743. sessionRetrieverParams: sessionRetrieverParams{
  1744. retrieveSessionParams: retrieveSessionParams{
  1745. {
  1746. screenName: user.IdentScreenName,
  1747. result: nil,
  1748. },
  1749. },
  1750. },
  1751. },
  1752. expectOutput: wire.SNACMessage{
  1753. Frame: wire.SNACFrame{
  1754. FoodGroup: wire.Kerberos,
  1755. SubGroup: wire.KerberosLoginSuccessResponse,
  1756. },
  1757. Body: wire.SNAC_0x050C_0x0003_KerberosLoginSuccessResponse{
  1758. RequestID: 54321,
  1759. Epoch: 1000,
  1760. ClientPrincipal: user.DisplayScreenName.String(),
  1761. ClientRealm: "AOL",
  1762. Tickets: []wire.KerberosTicket{
  1763. {
  1764. PVNO: 0x5,
  1765. EncTicket: []uint8{},
  1766. TicketRealm: "AOL",
  1767. ServicePrincipal: "im/boss",
  1768. ClientRealm: "AOL",
  1769. ClientPrincipal: user.DisplayScreenName.String(),
  1770. AuthTime: 1000,
  1771. StartTime: 1000,
  1772. EndTime: 87400,
  1773. Unknown4: 0x60000000,
  1774. Unknown5: 0x40000000,
  1775. ConnectionMetadata: wire.TLVBlock{
  1776. TLVList: wire.TLVList{
  1777. wire.NewTLVBE(wire.KerberosTLVBOSServerInfo, wire.KerberosBOSServerInfo{
  1778. Unknown: 1,
  1779. ConnectionInfo: wire.TLVBlock{
  1780. TLVList: wire.TLVList{
  1781. wire.NewTLVBE(wire.KerberosTLVHostname, "127.0.0.1:5190"),
  1782. wire.NewTLVBE(wire.KerberosTLVCookie, []byte("the-cookie")),
  1783. wire.NewTLVBE(wire.KerberosTLVConnSettings, wire.KerberosConnUseSSL),
  1784. wire.NewTLVBE(wire.KerberosTLVTLSCertName, "127.0.0.1"),
  1785. },
  1786. },
  1787. }),
  1788. },
  1789. },
  1790. },
  1791. },
  1792. },
  1793. },
  1794. },
  1795. {
  1796. name: "AIM account exists, incorrect password, login failed",
  1797. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostSSL: "127.0.0.1:5190"}, IsSSL: true},
  1798. timeNow: func() time.Time {
  1799. return time.Unix(1000, 0)
  1800. },
  1801. inputSNAC: wire.SNAC_0x050C_0x0002_KerberosLoginRequest{
  1802. RequestID: 54321,
  1803. ClientPrincipal: user.DisplayScreenName.String(),
  1804. TicketRequestMetadata: wire.TLVBlock{
  1805. TLVList: wire.TLVList{
  1806. wire.NewTLVBE(wire.KerberosTLVTicketRequest, wire.KerberosLoginRequestTicket{
  1807. Password: []byte("the_WRONG_password"),
  1808. }),
  1809. },
  1810. },
  1811. },
  1812. mockParams: mockParams{
  1813. userManagerParams: userManagerParams{
  1814. getUserParams: getUserParams{
  1815. {
  1816. screenName: user.IdentScreenName,
  1817. result: nil,
  1818. },
  1819. },
  1820. },
  1821. },
  1822. expectOutput: wire.SNACMessage{
  1823. Frame: wire.SNACFrame{
  1824. FoodGroup: wire.Kerberos,
  1825. SubGroup: wire.KerberosKerberosLoginErrResponse,
  1826. },
  1827. Body: wire.SNAC_0x050C_0x0004_KerberosLoginErrResponse{
  1828. KerbRequestID: 54321,
  1829. ScreenName: user.DisplayScreenName.String(),
  1830. ErrCode: wire.KerberosErrAuthFailure,
  1831. Message: "Auth failure",
  1832. },
  1833. },
  1834. },
  1835. {
  1836. name: "AIM account exists, correct roasted password, login OK",
  1837. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostSSL: "127.0.0.1:5190"}, IsSSL: true},
  1838. timeNow: func() time.Time {
  1839. return time.Unix(1000, 0)
  1840. },
  1841. inputSNAC: wire.SNAC_0x050C_0x0002_KerberosLoginRequest{
  1842. RequestID: 54321,
  1843. ClientPrincipal: user.DisplayScreenName.String(),
  1844. TicketRequestMetadata: wire.TLVBlock{
  1845. TLVList: wire.TLVList{
  1846. wire.NewTLVBE(wire.KerberosTLVTicketRequest, wire.KerberosLoginRequestTicket{
  1847. Version: 4,
  1848. Password: wire.RoastKerberosPassword([]byte("the_password")),
  1849. }),
  1850. },
  1851. },
  1852. },
  1853. mockParams: mockParams{
  1854. userManagerParams: userManagerParams{
  1855. getUserParams: getUserParams{
  1856. {
  1857. screenName: user.IdentScreenName,
  1858. result: &user,
  1859. },
  1860. },
  1861. },
  1862. cookieBakerParams: cookieBakerParams{
  1863. cookieIssueParams: cookieIssueParams{
  1864. {
  1865. ttlIn: state.DefaultCookieTTL,
  1866. dataIn: func() []byte {
  1867. loginCookie := state.ServerCookie{
  1868. TokenTTL: uint32((state.DefaultCookieTTL).Seconds()),
  1869. Service: wire.BOS,
  1870. ScreenName: user.DisplayScreenName,
  1871. ClientID: "",
  1872. MultiConnFlag: uint8(wire.MultiConnFlagsRecentClient),
  1873. KerberosAuth: 1,
  1874. }
  1875. buf := &bytes.Buffer{}
  1876. assert.NoError(t, wire.MarshalBE(loginCookie, buf))
  1877. return buf.Bytes()
  1878. }(),
  1879. cookieOut: []byte("the-cookie"),
  1880. },
  1881. },
  1882. },
  1883. sessionRetrieverParams: sessionRetrieverParams{
  1884. retrieveSessionParams: retrieveSessionParams{
  1885. {
  1886. screenName: user.IdentScreenName,
  1887. result: nil,
  1888. },
  1889. },
  1890. },
  1891. },
  1892. expectOutput: wire.SNACMessage{
  1893. Frame: wire.SNACFrame{
  1894. FoodGroup: wire.Kerberos,
  1895. SubGroup: wire.KerberosLoginSuccessResponse,
  1896. },
  1897. Body: wire.SNAC_0x050C_0x0003_KerberosLoginSuccessResponse{
  1898. RequestID: 54321,
  1899. Epoch: 1000,
  1900. ClientPrincipal: user.DisplayScreenName.String(),
  1901. ClientRealm: "AOL",
  1902. Tickets: []wire.KerberosTicket{
  1903. {
  1904. PVNO: 0x5,
  1905. EncTicket: []uint8{},
  1906. TicketRealm: "AOL",
  1907. ServicePrincipal: "im/boss",
  1908. ClientRealm: "AOL",
  1909. ClientPrincipal: user.DisplayScreenName.String(),
  1910. AuthTime: 1000,
  1911. StartTime: 1000,
  1912. EndTime: 87400,
  1913. Unknown4: 0x60000000,
  1914. Unknown5: 0x40000000,
  1915. ConnectionMetadata: wire.TLVBlock{
  1916. TLVList: wire.TLVList{
  1917. wire.NewTLVBE(wire.KerberosTLVBOSServerInfo, wire.KerberosBOSServerInfo{
  1918. Unknown: 1,
  1919. ConnectionInfo: wire.TLVBlock{
  1920. TLVList: wire.TLVList{
  1921. wire.NewTLVBE(wire.KerberosTLVHostname, "127.0.0.1:5190"),
  1922. wire.NewTLVBE(wire.KerberosTLVCookie, []byte("the-cookie")),
  1923. wire.NewTLVBE(wire.KerberosTLVConnSettings, wire.KerberosConnUseSSL),
  1924. wire.NewTLVBE(wire.KerberosTLVTLSCertName, "127.0.0.1"),
  1925. },
  1926. },
  1927. }),
  1928. },
  1929. },
  1930. },
  1931. },
  1932. },
  1933. },
  1934. },
  1935. {
  1936. name: "AIM account exists, incorrect roasted password, login failed",
  1937. endpointCfg: config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostSSL: "127.0.0.1:5190"}, IsSSL: true},
  1938. timeNow: func() time.Time {
  1939. return time.Unix(1000, 0)
  1940. },
  1941. inputSNAC: wire.SNAC_0x050C_0x0002_KerberosLoginRequest{
  1942. RequestID: 54321,
  1943. ClientPrincipal: user.DisplayScreenName.String(),
  1944. TicketRequestMetadata: wire.TLVBlock{
  1945. TLVList: wire.TLVList{
  1946. wire.NewTLVBE(wire.KerberosTLVTicketRequest, wire.KerberosLoginRequestTicket{
  1947. Version: 4,
  1948. Password: wire.RoastKerberosPassword([]byte("the_WRONG_password")),
  1949. }),
  1950. },
  1951. },
  1952. },
  1953. mockParams: mockParams{
  1954. userManagerParams: userManagerParams{
  1955. getUserParams: getUserParams{
  1956. {
  1957. screenName: user.IdentScreenName,
  1958. result: &user,
  1959. },
  1960. },
  1961. },
  1962. },
  1963. expectOutput: wire.SNACMessage{
  1964. Frame: wire.SNACFrame{
  1965. FoodGroup: wire.Kerberos,
  1966. SubGroup: wire.KerberosKerberosLoginErrResponse,
  1967. },
  1968. Body: wire.SNAC_0x050C_0x0004_KerberosLoginErrResponse{
  1969. KerbRequestID: 54321,
  1970. ScreenName: user.DisplayScreenName.String(),
  1971. ErrCode: wire.KerberosErrAuthFailure,
  1972. Message: "Auth failure",
  1973. },
  1974. },
  1975. },
  1976. }
  1977. for _, tc := range cases {
  1978. t.Run(tc.name, func(t *testing.T) {
  1979. userManager := newMockUserManager(t)
  1980. for _, params := range tc.mockParams.userManagerParams.getUserParams {
  1981. userManager.EXPECT().
  1982. User(matchContext(), params.screenName).
  1983. Return(params.result, params.err)
  1984. }
  1985. cookieBaker := newMockCookieBaker(t)
  1986. for _, params := range tc.mockParams.cookieIssueParams {
  1987. cookieBaker.EXPECT().
  1988. Issue(params.dataIn, params.ttlIn).
  1989. Return(params.cookieOut, params.err)
  1990. }
  1991. sessionRetriever := newMockSessionRetriever(t)
  1992. for _, params := range tc.mockParams.retrieveSessionParams {
  1993. sessionRetriever.EXPECT().
  1994. RetrieveSession(params.screenName).
  1995. Return(params.result)
  1996. }
  1997. feedbagManager := newMockFeedbagManager(t)
  1998. feedbagManager.EXPECT().Feedbag(matchContext(), mock.Anything).Return(nil, nil).Maybe()
  1999. svc := AuthService{
  2000. config: tc.cfg,
  2001. cookieBaker: cookieBaker,
  2002. userManager: userManager,
  2003. sessionRetriever: sessionRetriever,
  2004. feedbagManager: feedbagManager,
  2005. timeNow: tc.timeNow,
  2006. maxConcurrentLoginsPerUser: 2,
  2007. createAccount: tc.createAccount,
  2008. logger: slog.Default(),
  2009. }
  2010. outputSNAC, err := svc.KerberosLogin(context.Background(), tc.inputSNAC, tc.endpointCfg)
  2011. assert.ErrorIs(t, err, tc.wantErr)
  2012. assert.Equal(t, tc.expectOutput, outputSNAC)
  2013. })
  2014. }
  2015. }
  2016. func TestAuthService_BUCPChallengeRequest(t *testing.T) {
  2017. sessUUID := uuid.UUID{1, 2, 3}
  2018. cases := []struct {
  2019. // name is the unit test name
  2020. name string
  2021. // cfg is the app configuration
  2022. cfg config.Config
  2023. // inputSNAC is the SNAC sent from the client to the server
  2024. inputSNAC wire.SNAC_0x17_0x06_BUCPChallengeRequest
  2025. // mockParams is the list of params sent to mocks that satisfy this
  2026. // method's dependencies
  2027. mockParams mockParams
  2028. // expectOutput is the SNAC sent from the server to client
  2029. expectOutput wire.SNACMessage
  2030. // wantErr is the error we expect from the method
  2031. wantErr error
  2032. }{
  2033. {
  2034. name: "login with valid username, expect OK login response",
  2035. inputSNAC: wire.SNAC_0x17_0x06_BUCPChallengeRequest{
  2036. TLVRestBlock: wire.TLVRestBlock{
  2037. TLVList: wire.TLVList{
  2038. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "sn_user_a"),
  2039. },
  2040. },
  2041. },
  2042. mockParams: mockParams{
  2043. userManagerParams: userManagerParams{
  2044. getUserParams: getUserParams{
  2045. {
  2046. screenName: state.NewIdentScreenName("sn_user_a"),
  2047. result: &state.User{
  2048. IdentScreenName: state.NewIdentScreenName("sn_user_a"),
  2049. AuthKey: "auth_key_user_a",
  2050. },
  2051. },
  2052. },
  2053. },
  2054. },
  2055. expectOutput: wire.SNACMessage{
  2056. Frame: wire.SNACFrame{
  2057. FoodGroup: wire.BUCP,
  2058. SubGroup: wire.BUCPChallengeResponse,
  2059. },
  2060. Body: wire.SNAC_0x17_0x07_BUCPChallengeResponse{
  2061. AuthKey: "auth_key_user_a",
  2062. },
  2063. },
  2064. },
  2065. {
  2066. name: "login with invalid username, expect OK login response (Cfg.DisableAuth=true)",
  2067. cfg: config.Config{
  2068. DisableAuth: true,
  2069. },
  2070. inputSNAC: wire.SNAC_0x17_0x06_BUCPChallengeRequest{
  2071. TLVRestBlock: wire.TLVRestBlock{
  2072. TLVList: wire.TLVList{
  2073. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "sn_user_b"),
  2074. },
  2075. },
  2076. },
  2077. mockParams: mockParams{
  2078. userManagerParams: userManagerParams{
  2079. getUserParams: getUserParams{
  2080. {
  2081. screenName: state.NewIdentScreenName("sn_user_b"),
  2082. result: nil,
  2083. },
  2084. },
  2085. },
  2086. },
  2087. expectOutput: wire.SNACMessage{
  2088. Frame: wire.SNACFrame{
  2089. FoodGroup: wire.BUCP,
  2090. SubGroup: wire.BUCPChallengeResponse,
  2091. },
  2092. Body: wire.SNAC_0x17_0x07_BUCPChallengeResponse{
  2093. AuthKey: sessUUID.String(),
  2094. },
  2095. },
  2096. },
  2097. {
  2098. name: "login with invalid username, expect failed login response (Cfg.DisableAuth=false)",
  2099. inputSNAC: wire.SNAC_0x17_0x06_BUCPChallengeRequest{
  2100. TLVRestBlock: wire.TLVRestBlock{
  2101. TLVList: wire.TLVList{
  2102. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "sn_user_b"),
  2103. },
  2104. },
  2105. },
  2106. mockParams: mockParams{
  2107. userManagerParams: userManagerParams{
  2108. getUserParams: getUserParams{
  2109. {
  2110. screenName: state.NewIdentScreenName("sn_user_b"),
  2111. result: nil,
  2112. },
  2113. },
  2114. },
  2115. },
  2116. expectOutput: wire.SNACMessage{
  2117. Frame: wire.SNACFrame{
  2118. FoodGroup: wire.BUCP,
  2119. SubGroup: wire.BUCPLoginResponse,
  2120. },
  2121. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
  2122. TLVRestBlock: wire.TLVRestBlock{
  2123. TLVList: wire.TLVList{
  2124. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, uint16(0x01)),
  2125. },
  2126. },
  2127. },
  2128. },
  2129. },
  2130. {
  2131. name: "login fails on user manager lookup",
  2132. inputSNAC: wire.SNAC_0x17_0x06_BUCPChallengeRequest{
  2133. TLVRestBlock: wire.TLVRestBlock{
  2134. TLVList: wire.TLVList{
  2135. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "sn_user_b"),
  2136. },
  2137. },
  2138. },
  2139. mockParams: mockParams{
  2140. userManagerParams: userManagerParams{
  2141. getUserParams: getUserParams{
  2142. {
  2143. screenName: state.NewIdentScreenName("sn_user_b"),
  2144. err: io.EOF,
  2145. },
  2146. },
  2147. },
  2148. },
  2149. wantErr: io.EOF,
  2150. },
  2151. }
  2152. for _, tc := range cases {
  2153. t.Run(tc.name, func(t *testing.T) {
  2154. userManager := newMockUserManager(t)
  2155. for _, params := range tc.mockParams.userManagerParams.getUserParams {
  2156. userManager.EXPECT().
  2157. User(matchContext(), params.screenName).
  2158. Return(params.result, params.err)
  2159. }
  2160. svc := AuthService{
  2161. config: tc.cfg,
  2162. userManager: userManager,
  2163. logger: slog.Default(),
  2164. }
  2165. fnNewUUID := func() uuid.UUID {
  2166. return sessUUID
  2167. }
  2168. outputSNAC, err := svc.BUCPChallenge(context.Background(), tc.inputSNAC, fnNewUUID)
  2169. assert.ErrorIs(t, err, tc.wantErr)
  2170. assert.Equal(t, tc.expectOutput, outputSNAC)
  2171. })
  2172. }
  2173. }
  2174. func TestAuthService_RegisterChatSession_HappyPath(t *testing.T) {
  2175. instance := newTestInstance("ScreenName")
  2176. serverCookie := state.ServerCookie{
  2177. ChatCookie: "the-chat-cookie",
  2178. ScreenName: instance.DisplayScreenName(),
  2179. }
  2180. chatSessionRegistry := newMockChatSessionRegistry(t)
  2181. chatSessionRegistry.EXPECT().
  2182. AddSession(mock.Anything, serverCookie.ChatCookie, instance.DisplayScreenName(), mock.Anything).
  2183. Return(instance, nil)
  2184. chatCookieBuf := &bytes.Buffer{}
  2185. assert.NoError(t, wire.MarshalBE(serverCookie, chatCookieBuf))
  2186. svc := NewAuthService(config.Config{}, nil, nil, chatSessionRegistry, nil, nil, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
  2187. have, err := svc.RegisterChatSession(context.Background(), serverCookie, nil)
  2188. assert.NoError(t, err)
  2189. assert.Equal(t, instance, have)
  2190. }
  2191. func TestAuthService_RegisterBOSSession(t *testing.T) {
  2192. screenName := state.DisplayScreenName("UserScreenName")
  2193. aimAuthCookie := state.ServerCookie{
  2194. ScreenName: screenName,
  2195. }
  2196. uin := state.DisplayScreenName("100003")
  2197. icqAuthCookie := state.ServerCookie{
  2198. ScreenName: uin,
  2199. }
  2200. cases := []struct {
  2201. // name is the unit test name
  2202. name string
  2203. // cookieOut is the auth cookieOut that contains session information
  2204. cookie state.ServerCookie
  2205. // cfg is the server config
  2206. cfg config.Config
  2207. // createAccount is called to auto-create a missing user when DisableAuth is set
  2208. createAccount state.CreateAccountFunc
  2209. // mockParams is the list of params sent to mocks that satisfy this
  2210. // method's dependencies
  2211. mockParams mockParams
  2212. // wantSess asserts the values of one or more session properties
  2213. wantSess func(*state.SessionInstance) bool
  2214. // wantErr is the error we expect from the method
  2215. wantErr error
  2216. }{
  2217. {
  2218. name: "successfully register an AIM session",
  2219. cookie: aimAuthCookie,
  2220. mockParams: mockParams{
  2221. sessionRegistryParams: sessionRegistryParams{
  2222. addSessionParams: addSessionParams{
  2223. {
  2224. screenName: screenName,
  2225. doMultiSess: false,
  2226. result: newTestInstance(screenName),
  2227. },
  2228. },
  2229. },
  2230. userManagerParams: userManagerParams{
  2231. getUserParams: getUserParams{
  2232. {
  2233. screenName: screenName.IdentScreenName(),
  2234. result: &state.User{
  2235. IdentScreenName: screenName.IdentScreenName(),
  2236. DisplayScreenName: screenName,
  2237. },
  2238. },
  2239. },
  2240. },
  2241. accountManagerParams: accountManagerParams{
  2242. accountManagerConfirmStatusParams: accountManagerConfirmStatusParams{
  2243. {
  2244. screenName: screenName.IdentScreenName(),
  2245. confirmStatus: true,
  2246. },
  2247. },
  2248. },
  2249. bartItemManagerParams: bartItemManagerParams{
  2250. buddyIconMetadataParams: buddyIconMetadataParams{
  2251. {
  2252. screenName: screenName.IdentScreenName(),
  2253. result: &wire.BARTID{
  2254. Type: wire.BARTTypesBuddyIcon,
  2255. BARTInfo: wire.BARTInfo{
  2256. Flags: wire.BARTFlagsKnown,
  2257. Hash: []byte{'m', 'y', 'i', 'c', 'o', 'n'},
  2258. },
  2259. },
  2260. },
  2261. },
  2262. },
  2263. },
  2264. wantSess: func(instance *state.SessionInstance) bool {
  2265. want := wire.BARTID{
  2266. Type: wire.BARTTypesBuddyIcon,
  2267. BARTInfo: wire.BARTInfo{
  2268. Flags: wire.BARTFlagsKnown,
  2269. Hash: []byte{'m', 'y', 'i', 'c', 'o', 'n'},
  2270. },
  2271. }
  2272. has, hasIcon := instance.Session().BuddyIcon()
  2273. return assert.True(t, hasIcon) && assert.Equal(t, want, has)
  2274. },
  2275. },
  2276. {
  2277. name: "successfully register an AIM bot session",
  2278. cookie: aimAuthCookie,
  2279. mockParams: mockParams{
  2280. sessionRegistryParams: sessionRegistryParams{
  2281. addSessionParams: addSessionParams{
  2282. {
  2283. screenName: screenName,
  2284. doMultiSess: false,
  2285. result: newTestInstance(screenName),
  2286. },
  2287. },
  2288. },
  2289. userManagerParams: userManagerParams{
  2290. getUserParams: getUserParams{
  2291. {
  2292. screenName: screenName.IdentScreenName(),
  2293. result: &state.User{
  2294. IdentScreenName: screenName.IdentScreenName(),
  2295. DisplayScreenName: screenName,
  2296. IsBot: true,
  2297. },
  2298. },
  2299. },
  2300. },
  2301. accountManagerParams: accountManagerParams{
  2302. accountManagerConfirmStatusParams: accountManagerConfirmStatusParams{
  2303. {
  2304. screenName: screenName.IdentScreenName(),
  2305. confirmStatus: true,
  2306. },
  2307. },
  2308. },
  2309. bartItemManagerParams: bartItemManagerParams{
  2310. buddyIconMetadataParams: buddyIconMetadataParams{
  2311. {
  2312. screenName: screenName.IdentScreenName(),
  2313. result: nil,
  2314. },
  2315. },
  2316. },
  2317. },
  2318. wantSess: func(instance *state.SessionInstance) bool {
  2319. return instance.Session().AllUserInfoBitmask(wire.OServiceUserFlagBot)
  2320. },
  2321. },
  2322. {
  2323. name: "successfully register an ICQ session",
  2324. cookie: icqAuthCookie,
  2325. mockParams: mockParams{
  2326. sessionRegistryParams: sessionRegistryParams{
  2327. addSessionParams: addSessionParams{
  2328. {
  2329. screenName: uin,
  2330. result: newTestInstance(uin),
  2331. },
  2332. },
  2333. },
  2334. userManagerParams: userManagerParams{
  2335. getUserParams: getUserParams{
  2336. {
  2337. screenName: uin.IdentScreenName(),
  2338. result: &state.User{
  2339. IdentScreenName: uin.IdentScreenName(),
  2340. DisplayScreenName: uin,
  2341. },
  2342. },
  2343. },
  2344. },
  2345. accountManagerParams: accountManagerParams{
  2346. accountManagerConfirmStatusParams: accountManagerConfirmStatusParams{
  2347. {
  2348. screenName: uin.IdentScreenName(),
  2349. confirmStatus: true,
  2350. },
  2351. },
  2352. },
  2353. bartItemManagerParams: bartItemManagerParams{
  2354. buddyIconMetadataParams: buddyIconMetadataParams{
  2355. {
  2356. screenName: uin.IdentScreenName(),
  2357. result: nil,
  2358. },
  2359. },
  2360. },
  2361. },
  2362. wantSess: func(instance *state.SessionInstance) bool {
  2363. uinMatches := fmt.Sprintf("%d", instance.UIN()) == uin.String()
  2364. flagsMatch := instance.Session().AllUserInfoBitmask(wire.OServiceUserFlagICQ)
  2365. return uinMatches && flagsMatch
  2366. },
  2367. },
  2368. {
  2369. name: "user not found, DisableAuth false, return error",
  2370. cookie: aimAuthCookie,
  2371. cfg: config.Config{DisableAuth: false},
  2372. wantErr: errors.New("user not found"),
  2373. mockParams: mockParams{
  2374. userManagerParams: userManagerParams{
  2375. getUserParams: getUserParams{
  2376. {
  2377. screenName: screenName.IdentScreenName(),
  2378. result: nil,
  2379. },
  2380. },
  2381. },
  2382. },
  2383. },
  2384. {
  2385. name: "user suspended, return error without creating a session",
  2386. cookie: aimAuthCookie,
  2387. wantErr: errors.New("user suspended"),
  2388. mockParams: mockParams{
  2389. userManagerParams: userManagerParams{
  2390. getUserParams: getUserParams{
  2391. {
  2392. screenName: screenName.IdentScreenName(),
  2393. result: &state.User{
  2394. IdentScreenName: screenName.IdentScreenName(),
  2395. DisplayScreenName: screenName,
  2396. SuspendedStatus: wire.LoginErrSuspendedAccount,
  2397. },
  2398. },
  2399. },
  2400. },
  2401. },
  2402. },
  2403. {
  2404. name: "user deleted, return error without creating a session",
  2405. cookie: aimAuthCookie,
  2406. wantErr: errors.New("user suspended"),
  2407. mockParams: mockParams{
  2408. userManagerParams: userManagerParams{
  2409. getUserParams: getUserParams{
  2410. {
  2411. screenName: screenName.IdentScreenName(),
  2412. result: &state.User{
  2413. IdentScreenName: screenName.IdentScreenName(),
  2414. DisplayScreenName: screenName,
  2415. SuspendedStatus: wire.LoginErrDeletedAccount,
  2416. },
  2417. },
  2418. },
  2419. },
  2420. },
  2421. },
  2422. }
  2423. for _, tc := range cases {
  2424. t.Run(tc.name, func(t *testing.T) {
  2425. sessionRegistry := newMockSessionRegistry(t)
  2426. for _, params := range tc.mockParams.addSessionParams {
  2427. sessionRegistry.EXPECT().
  2428. AddSession(mock.Anything, params.screenName, params.doMultiSess, mock.Anything, mock.Anything).
  2429. Return(params.result, params.err)
  2430. }
  2431. userManager := newMockUserManager(t)
  2432. for _, params := range tc.mockParams.userManagerParams.getUserParams {
  2433. userManager.EXPECT().
  2434. User(matchContext(), params.screenName).
  2435. Return(params.result, nil).Once()
  2436. }
  2437. accountManager := newMockAccountManager(t)
  2438. for _, params := range tc.mockParams.accountManagerConfirmStatusParams {
  2439. accountManager.EXPECT().
  2440. ConfirmStatus(matchContext(), params.screenName).
  2441. Return(params.confirmStatus, nil)
  2442. }
  2443. bartItemManager := newMockBARTItemManager(t)
  2444. for _, params := range tc.mockParams.buddyIconMetadataParams {
  2445. bartItemManager.EXPECT().
  2446. BuddyIconMetadata(matchContext(), params.screenName).
  2447. Return(params.result, params.err)
  2448. }
  2449. svc := NewAuthService(tc.cfg, sessionRegistry, nil, nil, userManager, nil, nil, accountManager, bartItemManager, nil, wire.DefaultRateLimitClasses(), tc.createAccount, slog.Default())
  2450. have, err := svc.RegisterBOSSession(context.Background(), tc.cookie, nil)
  2451. if tc.wantErr != nil {
  2452. assert.ErrorContains(t, err, tc.wantErr.Error())
  2453. return
  2454. }
  2455. assert.NoError(t, err)
  2456. if tc.wantSess != nil {
  2457. assert.True(t, tc.wantSess(have))
  2458. }
  2459. })
  2460. }
  2461. }
  2462. func TestAuthService_RetrieveBOSSession_HappyPath(t *testing.T) {
  2463. instance := newTestInstance("screenName", sessOptSignonComplete)
  2464. aimAuthCookie := state.ServerCookie{
  2465. ScreenName: instance.DisplayScreenName(),
  2466. SessionNum: instance.Num(),
  2467. }
  2468. sessionRetriever := newMockSessionRetriever(t)
  2469. sessionRetriever.EXPECT().
  2470. RetrieveSession(instance.IdentScreenName()).
  2471. Return(instance.Session())
  2472. userManager := newMockUserManager(t)
  2473. userManager.EXPECT().
  2474. User(matchContext(), instance.IdentScreenName()).
  2475. Return(&state.User{IdentScreenName: instance.IdentScreenName()}, nil)
  2476. svc := NewAuthService(config.Config{}, nil, sessionRetriever, nil, userManager, nil, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
  2477. have, err := svc.RetrieveBOSSession(context.Background(), aimAuthCookie)
  2478. assert.NoError(t, err)
  2479. assert.Equal(t, instance, have)
  2480. }
  2481. func TestAuthService_RetrieveBOSSession_SessionNotFound(t *testing.T) {
  2482. instance := newTestInstance("screenName")
  2483. aimAuthCookie := state.ServerCookie{
  2484. ScreenName: instance.DisplayScreenName(),
  2485. SessionNum: instance.Num(),
  2486. }
  2487. sessionRetriever := newMockSessionRetriever(t)
  2488. sessionRetriever.EXPECT().
  2489. RetrieveSession(instance.IdentScreenName()).
  2490. Return(nil)
  2491. userManager := newMockUserManager(t)
  2492. userManager.EXPECT().
  2493. User(matchContext(), instance.IdentScreenName()).
  2494. Return(&state.User{IdentScreenName: instance.IdentScreenName()}, nil)
  2495. svc := NewAuthService(config.Config{}, nil, sessionRetriever, nil, userManager, nil, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
  2496. have, err := svc.RetrieveBOSSession(context.Background(), aimAuthCookie)
  2497. assert.NoError(t, err)
  2498. assert.Nil(t, have)
  2499. }
  2500. func TestAuthService_SignoutChat(t *testing.T) {
  2501. tests := []struct {
  2502. // name is the unit test name
  2503. name string
  2504. // instance is the session of the user signing out
  2505. instance *state.SessionInstance
  2506. // mockParams is the list of params sent to mocks that satisfy this
  2507. // method's dependencies
  2508. mockParams mockParams
  2509. }{
  2510. {
  2511. name: "user signs out of chat room, room is empty after user leaves",
  2512. instance: newTestInstance("me", sessOptCannedSignonTime, sessOptChatRoomCookie("the-chat-cookie")),
  2513. mockParams: mockParams{
  2514. chatMessageRelayerParams: chatMessageRelayerParams{
  2515. chatRelayToAllExceptParams: chatRelayToAllExceptParams{
  2516. {
  2517. screenName: state.NewIdentScreenName("me"),
  2518. message: wire.SNACMessage{
  2519. Frame: wire.SNACFrame{
  2520. FoodGroup: wire.Chat,
  2521. SubGroup: wire.ChatUsersLeft,
  2522. },
  2523. Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{
  2524. Users: []wire.TLVUserInfo{
  2525. newTestInstance("me", sessOptCannedSignonTime, sessOptChatRoomCookie("the-chat-cookie")).Session().TLVUserInfo(),
  2526. },
  2527. },
  2528. },
  2529. },
  2530. },
  2531. },
  2532. sessionRegistryParams: sessionRegistryParams{
  2533. removeSessionParams: removeSessionParams{
  2534. {
  2535. screenName: state.NewIdentScreenName("me"),
  2536. },
  2537. },
  2538. },
  2539. },
  2540. },
  2541. {
  2542. name: "user signs out of chat room, room is not empty after user leaves",
  2543. instance: newTestInstance("me", sessOptCannedSignonTime, sessOptChatRoomCookie("the-chat-cookie")),
  2544. mockParams: mockParams{
  2545. chatMessageRelayerParams: chatMessageRelayerParams{
  2546. chatRelayToAllExceptParams: chatRelayToAllExceptParams{
  2547. {
  2548. screenName: state.NewIdentScreenName("me"),
  2549. message: wire.SNACMessage{
  2550. Frame: wire.SNACFrame{
  2551. FoodGroup: wire.Chat,
  2552. SubGroup: wire.ChatUsersLeft,
  2553. },
  2554. Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{
  2555. Users: []wire.TLVUserInfo{
  2556. newTestInstance("me", sessOptCannedSignonTime, sessOptChatRoomCookie("the-chat-cookie")).Session().TLVUserInfo(),
  2557. },
  2558. },
  2559. },
  2560. },
  2561. },
  2562. },
  2563. sessionRegistryParams: sessionRegistryParams{
  2564. removeSessionParams: removeSessionParams{
  2565. {
  2566. screenName: state.NewIdentScreenName("me"),
  2567. },
  2568. },
  2569. },
  2570. },
  2571. },
  2572. }
  2573. for _, tt := range tests {
  2574. t.Run(tt.name, func(t *testing.T) {
  2575. chatMessageRelayer := newMockChatMessageRelayer(t)
  2576. for _, params := range tt.mockParams.chatRelayToAllExceptParams {
  2577. chatMessageRelayer.EXPECT().
  2578. RelayToAllExcept(matchContext(), tt.instance.ChatRoomCookie(), params.screenName, params.message)
  2579. }
  2580. sessionManager := newMockChatSessionRegistry(t)
  2581. for _, params := range tt.mockParams.removeSessionParams {
  2582. sessionManager.EXPECT().
  2583. RemoveSession(matchUserSession(params.screenName))
  2584. }
  2585. svc := NewAuthService(config.Config{}, nil, nil, sessionManager, nil, nil, chatMessageRelayer, nil, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
  2586. svc.SignoutChat(context.Background(), tt.instance.Session())
  2587. })
  2588. }
  2589. }
  2590. func TestAuthService_Signout(t *testing.T) {
  2591. tests := []struct {
  2592. // name is the unit test name
  2593. name string
  2594. // instance is the session of the user signing out
  2595. instance *state.SessionInstance
  2596. // wantErr is the error we expect from the method
  2597. wantErr error
  2598. // mockParams is the list of params sent to mocks that satisfy this
  2599. // method's dependencies
  2600. mockParams mockParams
  2601. }{
  2602. {
  2603. name: "user signs out of chat room, room is empty after user leaves",
  2604. instance: newTestInstance("me", sessOptCannedSignonTime),
  2605. mockParams: mockParams{
  2606. buddyBroadcasterParams: buddyBroadcasterParams{
  2607. broadcastBuddyDepartedParams: broadcastBuddyDepartedParams{
  2608. {
  2609. screenName: state.NewIdentScreenName("me"),
  2610. },
  2611. },
  2612. },
  2613. sessionRegistryParams: sessionRegistryParams{
  2614. removeSessionParams: removeSessionParams{
  2615. {
  2616. screenName: state.NewIdentScreenName("me"),
  2617. },
  2618. },
  2619. },
  2620. },
  2621. },
  2622. }
  2623. for _, tt := range tests {
  2624. t.Run(tt.name, func(t *testing.T) {
  2625. sessionManager := newMockSessionRegistry(t)
  2626. for _, params := range tt.mockParams.removeSessionParams {
  2627. sessionManager.EXPECT().RemoveSession(matchUserSession(params.screenName))
  2628. }
  2629. svc := NewAuthService(config.Config{}, sessionManager, nil, nil, nil, nil, nil, nil, nil, nil, wire.DefaultRateLimitClasses(), nil, slog.Default())
  2630. svc.Signout(context.Background(), tt.instance.Session())
  2631. })
  2632. }
  2633. }
  2634. func TestBuildLinkedAccountsXML(t *testing.T) {
  2635. cases := []struct {
  2636. name string
  2637. screenName state.IdentScreenName
  2638. linkedNames []state.IdentScreenName
  2639. wantXML string
  2640. wantErr bool
  2641. }{
  2642. {
  2643. name: "primary account with no linked accounts",
  2644. screenName: state.NewIdentScreenName("PrimaryUser"),
  2645. linkedNames: []state.IdentScreenName{},
  2646. wantXML: `<SET SETID="1"><RESREC TYPE="PRIMARY-ACCOUNT" ID="1"><n>primaryuser</n></RESREC></SET>`,
  2647. },
  2648. {
  2649. name: "primary account with one linked account",
  2650. screenName: state.NewIdentScreenName("PrimaryUser"),
  2651. linkedNames: []state.IdentScreenName{
  2652. state.NewIdentScreenName("LinkedUser1"),
  2653. },
  2654. wantXML: `<SET SETID="1"><RESREC TYPE="PRIMARY-ACCOUNT" ID="1"><n>primaryuser</n></RESREC><RESREC TYPE="LINKED-ACCOUNT" ID="2"><n>linkeduser1</n></RESREC></SET>`,
  2655. },
  2656. {
  2657. name: "primary account with multiple linked accounts",
  2658. screenName: state.NewIdentScreenName("PrimaryUser"),
  2659. linkedNames: []state.IdentScreenName{
  2660. state.NewIdentScreenName("LinkedUser1"),
  2661. state.NewIdentScreenName("LinkedUser2"),
  2662. state.NewIdentScreenName("LinkedUser3"),
  2663. },
  2664. wantXML: `<SET SETID="1"><RESREC TYPE="PRIMARY-ACCOUNT" ID="1"><n>primaryuser</n></RESREC><RESREC TYPE="LINKED-ACCOUNT" ID="2"><n>linkeduser1</n></RESREC><RESREC TYPE="LINKED-ACCOUNT" ID="3"><n>linkeduser2</n></RESREC><RESREC TYPE="LINKED-ACCOUNT" ID="4"><n>linkeduser3</n></RESREC></SET>`,
  2665. },
  2666. }
  2667. for _, tc := range cases {
  2668. t.Run(tc.name, func(t *testing.T) {
  2669. got, err := buildLinkedAccountsXML(tc.screenName, tc.linkedNames)
  2670. if tc.wantErr {
  2671. assert.Error(t, err)
  2672. return
  2673. }
  2674. assert.NoError(t, err)
  2675. assert.Equal(t, tc.wantXML, got)
  2676. })
  2677. }
  2678. }
  2679. func TestAuthService_addLinkedAccountsTLV(t *testing.T) {
  2680. cases := []struct {
  2681. name string
  2682. screenName state.DisplayScreenName
  2683. // feedbagItems is what feedbagManager.Feedbag returns
  2684. feedbagItems []wire.FeedbagItem
  2685. feedbagErr error
  2686. // wantTLVCount is the expected number of TLVs after the call
  2687. wantTLVCount int
  2688. wantErr bool
  2689. }{
  2690. {
  2691. name: "no linked accounts, TLV list unchanged",
  2692. screenName: "PrimaryUser",
  2693. feedbagItems: nil,
  2694. wantTLVCount: 0,
  2695. },
  2696. {
  2697. name: "one linked account, TLV appended",
  2698. screenName: "PrimaryUser",
  2699. feedbagItems: []wire.FeedbagItem{
  2700. {ClassID: wire.FeedbagClassIdAlInfo, Name: "linkeduser1"},
  2701. },
  2702. wantTLVCount: 1,
  2703. },
  2704. {
  2705. name: "multiple linked accounts, single TLV appended",
  2706. screenName: "PrimaryUser",
  2707. feedbagItems: []wire.FeedbagItem{
  2708. {ClassID: wire.FeedbagClassIdAlInfo, Name: "linkeduser1"},
  2709. {ClassID: wire.FeedbagClassIdAlInfo, Name: "linkeduser2"},
  2710. },
  2711. wantTLVCount: 1,
  2712. },
  2713. {
  2714. name: "feedbagManager returns error, error propagated",
  2715. screenName: "PrimaryUser",
  2716. feedbagErr: io.EOF,
  2717. wantErr: true,
  2718. },
  2719. }
  2720. for _, tc := range cases {
  2721. t.Run(tc.name, func(t *testing.T) {
  2722. feedbagManager := newMockFeedbagManager(t)
  2723. feedbagManager.EXPECT().
  2724. Feedbag(matchContext(), state.NewIdentScreenName(string(tc.screenName))).
  2725. Return(tc.feedbagItems, tc.feedbagErr)
  2726. svc := AuthService{feedbagManager: feedbagManager}
  2727. tlvs := wire.TLVList{}
  2728. err := svc.addLinkedAccountsTLV(context.Background(), tc.screenName, &tlvs)
  2729. if tc.wantErr {
  2730. assert.Error(t, err)
  2731. return
  2732. }
  2733. assert.NoError(t, err)
  2734. assert.Len(t, tlvs, tc.wantTLVCount)
  2735. if tc.wantTLVCount > 0 {
  2736. assert.Equal(t, wire.OServiceTLVTagsLinkedAccounts, tlvs[0].Tag)
  2737. }
  2738. })
  2739. }
  2740. }