auth_test.go 78 KB

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