types.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. // Package foodgroup implements OSCAR food group business logic.
  2. //
  3. // The OSCAR protocol passes messages in SNAC (Simple Network Atomic
  4. // Communication) format. SNAC messages are grouped by "food groups" (get it?
  5. // snack, snac, foodgroup...). Each food group is responsible for a discrete
  6. // piece of functionality, such as buddy list management (Feedbag), instant
  7. // messaging (ICBM), and chat messaging (Chat).
  8. //
  9. // Each food group operation is represented by a struct type. The methods
  10. // correspond 1:1 to each food group operation. Each food group operation is
  11. // typically triggered by a client request. The operation may return a
  12. // response. As such, methods receive client requests via SNAC frame and
  13. // body parameters and send responses via returned SNAC objects.
  14. //
  15. // The following is a typical food group method signature. This example
  16. // illustrates the ICBM ChannelMsgToHost operation.
  17. //
  18. // ChannelMsgToHost(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*wire.SNACMessage, error)
  19. //
  20. // Params:
  21. // - ctx context.Context is the client request context.
  22. // - instance *state.SessionInstance is the client's session object.
  23. // - inFrame wire.SNACFrame is the request SNAC frame that contains the food group and subgroup parameters.
  24. // - inBody wire.SNAC_0x04_0x06_ICBMChannelMsgToHost contains the body of the SNAC message. In this case, it contains instant message text and metadata.
  25. //
  26. // ChannelMsgToHost optionally sends a client response by returning
  27. // *wire.SNACMessage. For operations that always send client responses,
  28. // the methods return wire.SNACMessage value types (not pointer types).
  29. // Methods for operations that never send client responses do not return
  30. // wire.SNACMessage values.
  31. //
  32. // The foodgroup package delegates responsibility for message transport, user
  33. // retrieval, and session management to callers via several interface types.
  34. package foodgroup
  35. import (
  36. "context"
  37. "net/mail"
  38. "time"
  39. "github.com/mk6i/open-oscar-server/state"
  40. "github.com/mk6i/open-oscar-server/wire"
  41. )
  42. // AccountManager is the interface for managing a user's account settings.
  43. type AccountManager interface {
  44. // ConfirmStatus returns whether a user account has been confirmed.
  45. ConfirmStatus(ctx context.Context, screenName state.IdentScreenName) (bool, error)
  46. // EmailAddress looks up a user's email address by screen name.
  47. EmailAddress(ctx context.Context, screenName state.IdentScreenName) (*mail.Address, error)
  48. // RegStatus looks up a user's registration status by screen name.
  49. // It returns one of the following values:
  50. // - wire.AdminInfoRegStatusFullDisclosure
  51. // - wire.AdminInfoRegStatusLimitDisclosure
  52. // - wire.AdminInfoRegStatusNoDisclosure
  53. RegStatus(ctx context.Context, screenName state.IdentScreenName) (uint16, error)
  54. // SetUserPassword sets the user's password hashes and auth key.
  55. SetUserPassword(ctx context.Context, screenName state.IdentScreenName, newPassword string) error
  56. // UpdateConfirmStatus sets whether a user account has been confirmed.
  57. UpdateConfirmStatus(ctx context.Context, screenName state.IdentScreenName, confirmStatus bool) error
  58. // UpdateDisplayScreenName updates the user's display screen name, which is
  59. // the screen name visible in the OSCAR client. It derives the user
  60. // identifier from the display screen name.
  61. UpdateDisplayScreenName(ctx context.Context, displayScreenName state.DisplayScreenName) error
  62. // UpdateEmailAddress changes a user's email address.
  63. UpdateEmailAddress(ctx context.Context, screenName state.IdentScreenName, emailAddress *mail.Address) error
  64. // UpdateRegStatus updates a user's registration status.
  65. // The regStatus param can be one of the following values:
  66. // - wire.AdminInfoRegStatusFullDisclosure
  67. // - wire.AdminInfoRegStatusLimitDisclosure
  68. // - wire.AdminInfoRegStatusNoDisclosure
  69. UpdateRegStatus(ctx context.Context, screenName state.IdentScreenName, regStatus uint16) error
  70. // User returns all attributes for a user.
  71. User(ctx context.Context, screenName state.IdentScreenName) (*state.User, error)
  72. }
  73. // buddyBroadcaster defines methods for broadcasting buddy presence and visibility events
  74. // to other sessions. These events notify users when a buddy comes online, goes offline,
  75. // or changes visibility status.
  76. type buddyBroadcaster interface {
  77. // BroadcastBuddyArrived notifies all relevant users that the given user has come online.
  78. BroadcastBuddyArrived(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo) error
  79. // BroadcastBuddyDeparted notifies all relevant users that the given user has gone offline.
  80. BroadcastBuddyDeparted(ctx context.Context, screenName state.IdentScreenName) error
  81. // BroadcastVisibility sends presence updates to the specified filter list.
  82. // If sendDepartures is true, departure events are sent as well.
  83. BroadcastVisibility(ctx context.Context, you *state.SessionInstance, filter []state.IdentScreenName, sendDepartures bool) error
  84. }
  85. // BARTItemManager is the interface for managing BART (Buddy Art) assets.
  86. type BARTItemManager interface {
  87. // BARTItem retrieves a BART asset by its hash.
  88. BARTItem(ctx context.Context, hash []byte) ([]byte, error)
  89. // BuddyIconMetadata retrieves a user's buddy icon metadata. It returns nil
  90. // if the user does not have a buddy icon.
  91. BuddyIconMetadata(ctx context.Context, screenName state.IdentScreenName) (*wire.BARTID, error)
  92. // InsertBARTItem creates or updates a BART asset and blob hash.
  93. InsertBARTItem(ctx context.Context, hash []byte, blob []byte, itemType uint16) error
  94. // ListBARTItems returns BART assets filtered by type.
  95. ListBARTItems(ctx context.Context, itemType uint16) ([]state.BARTItem, error)
  96. // DeleteBARTItem deletes a BART asset by hash.
  97. DeleteBARTItem(ctx context.Context, hash []byte) error
  98. }
  99. // ContactPreAuthorizer evaluates and records contact pre-authorization: when
  100. // owner requires authorization to be added, requester must hold a grant from owner.
  101. type ContactPreAuthorizer interface {
  102. // RequiresAuthorization reports whether requester must obtain authorization
  103. // from owner before adding owner as a buddy. Returns false if owner does
  104. // not require authorization or has already pre-authorized requester.
  105. RequiresAuthorization(ctx context.Context, owner, requester state.IdentScreenName) (bool, error)
  106. // RecordPreAuth records that owner has pre-authorized requester to add
  107. // owner as a buddy without an authorization prompt. No-ops when either
  108. // user is not registered.
  109. RecordPreAuth(ctx context.Context, owner, requester state.IdentScreenName) error
  110. }
  111. // BuddyAddedNotifierDeduper suppresses duplicate "you were added" notifications.
  112. // A record indicates requesterScreenName was added to granterScreenName's list.
  113. type BuddyAddedNotifierDeduper interface {
  114. // HasBuddyAddedNotification reports whether a "you were added" notification
  115. // has already been sent for requester being added by granter.
  116. HasBuddyAddedNotification(ctx context.Context, granter, requester state.IdentScreenName) (bool, error)
  117. // RecordBuddyAddedNotification records that a "you were added" notification
  118. // has been sent for requester being added by granter. No-ops when either
  119. // user is not registered.
  120. RecordBuddyAddedNotification(ctx context.Context, granter, requester state.IdentScreenName) error
  121. }
  122. // RelationshipFetcher is the interface for retrieving relationships between users.
  123. type RelationshipFetcher interface {
  124. // AllRelationships retrieves the relationships between the specified user (`me`)
  125. // and other users.
  126. AllRelationships(ctx context.Context, me state.IdentScreenName, filter []state.IdentScreenName) ([]state.Relationship, error)
  127. // Relationship retrieves the relationship between the specified user (`me`)
  128. // and another user (`them`).
  129. Relationship(ctx context.Context, me state.IdentScreenName, them state.IdentScreenName) (state.Relationship, error)
  130. }
  131. // ChatMessageRelayer defines the interface for sending messages to chat room
  132. // participants.
  133. type ChatMessageRelayer interface {
  134. // AllSessions returns all chat room participants. Returns
  135. // ErrChatRoomNotFound if the room does not exist.
  136. AllSessions(chatCookie string) []*state.Session
  137. // RelayToAllExcept sends a message to all chat room participants except
  138. // for the participant with a particular screen name. Returns
  139. // ErrChatRoomNotFound if the room does not exist for cookie.
  140. RelayToAllExcept(ctx context.Context, chatCookie string, except state.IdentScreenName, msg wire.SNACMessage)
  141. // RelayToScreenName sends a message to a chat room user. Returns
  142. // ErrChatRoomNotFound if the room does not exist for cookie.
  143. RelayToScreenName(ctx context.Context, chatCookie string, recipient state.IdentScreenName, msg wire.SNACMessage)
  144. }
  145. // ChatRoomRegistry defines the interface for storing and retrieving chat
  146. // rooms in a persistent store. The persistent store has two purposes:
  147. // - Remember user-created chat rooms (exchange 4) so that clients can reconnect
  148. // to the rooms following server restarts.
  149. // - Keep track of public chat room created by the server operator (exchange 5).
  150. // User's can only join public chat rooms that exist in the room registry.
  151. type ChatRoomRegistry interface {
  152. // ChatRoomByCookie looks up a chat room by exchange. Returns
  153. // state.ErrChatRoomNotFound if the room does not exist for cookie.
  154. ChatRoomByCookie(ctx context.Context, chatCookie string) (state.ChatRoom, error)
  155. // ChatRoomByName looks up a chat room by exchange and name. Returns
  156. // state.ErrChatRoomNotFound if the room does not exist for exchange and name.
  157. ChatRoomByName(ctx context.Context, exchange uint16, name string) (state.ChatRoom, error)
  158. // CreateChatRoom creates a new chat room.
  159. CreateChatRoom(ctx context.Context, chatRoom *state.ChatRoom) error
  160. }
  161. // ChatSessionRegistry defines the interface for adding and removing chat
  162. // sessions.
  163. type ChatSessionRegistry interface {
  164. // AddSession adds a session to the chat session manager. The chatCookie
  165. // param identifies the chat room to which screenName is added. It returns
  166. // the newly created session instance registered in the chat session
  167. // manager.
  168. AddSession(ctx context.Context, chatCookie string, screenName state.DisplayScreenName, cfg ...func(sess *state.Session)) (*state.SessionInstance, error)
  169. // RemoveSession removes a session from the chat session manager.
  170. RemoveSession(sess *state.Session)
  171. }
  172. // ClientSideBuddyListManager defines operations for managing a user's buddy list,
  173. // including permissions like allow/deny and buddy group modifications.
  174. //
  175. // This interface manages a client-side buddy list that is temporarily copied
  176. // to the server on a per-session basis. The list is cleared when the user signs out.
  177. type ClientSideBuddyListManager interface {
  178. // AddBuddy adds 'them' to 'me's buddy list.
  179. AddBuddy(ctx context.Context, me state.IdentScreenName, them state.IdentScreenName) error
  180. // DenyBuddy blocks messages and presence visibility from 'them' to 'me'.
  181. DenyBuddy(ctx context.Context, me state.IdentScreenName, them state.IdentScreenName) error
  182. // PermitBuddy allows messages and presence visibility from 'them' to 'me',
  183. // potentially overriding deny settings.
  184. PermitBuddy(ctx context.Context, me state.IdentScreenName, them state.IdentScreenName) error
  185. // RemoveBuddy removes 'them' from 'me's buddy list. Does not affect permit/deny status.
  186. RemoveBuddy(ctx context.Context, me state.IdentScreenName, them state.IdentScreenName) error
  187. // RemoveDenyBuddy removes 'them' from 'me's deny list, restoring default
  188. // visibility and messaging behavior.
  189. RemoveDenyBuddy(ctx context.Context, me state.IdentScreenName, them state.IdentScreenName) error
  190. // RemovePermitBuddy removes 'them' from 'me's permit list, restoring default
  191. // visibility and messaging behavior.
  192. RemovePermitBuddy(ctx context.Context, me state.IdentScreenName, them state.IdentScreenName) error
  193. // SetPDMode sets the permit/deny mode (e.g., allow all, deny all, permit
  194. // some) for 'me'. It clears any existing permit/deny records.
  195. SetPDMode(ctx context.Context, me state.IdentScreenName, pdMode wire.FeedbagPDMode) error
  196. }
  197. // CookieBaker defines methods for issuing and verifying AIM authentication tokens ("cookies").
  198. // These tokens are used for authenticating client sessions with AIM services.
  199. type CookieBaker interface {
  200. // Crack verifies and decodes a previously issued authentication token.
  201. // Returns the original payload if the token is valid.
  202. Crack(data []byte) ([]byte, error)
  203. // Issue creates a new authentication token from the given payload.
  204. // The resulting token can later be verified using Crack.
  205. Issue(data []byte) ([]byte, error)
  206. }
  207. // FeedbagManager is the interface for reading and modifying server-side buddy
  208. // lists (feedbag).
  209. type FeedbagManager interface {
  210. // Feedbag fetches the contents of a user's feedbag.
  211. Feedbag(ctx context.Context, screenName state.IdentScreenName) ([]wire.FeedbagItem, error)
  212. // FeedbagDelete deletes an entry from a user's feedbag.
  213. FeedbagDelete(ctx context.Context, screenName state.IdentScreenName, items []wire.FeedbagItem) error
  214. // FeedbagLastModified returns the last time a user's feedbag was updated.
  215. FeedbagLastModified(ctx context.Context, screenName state.IdentScreenName) (time.Time, error)
  216. // FeedbagUpsert upserts an entry to a user's feedbag.
  217. FeedbagUpsert(ctx context.Context, screenName state.IdentScreenName, items []wire.FeedbagItem) error
  218. // UseFeedbag sets the user's session to use feedbag instead of the default
  219. // client-side buddy list.
  220. UseFeedbag(ctx context.Context, screenName state.IdentScreenName) error
  221. }
  222. // ICQUserFinder defines methods for searching ICQ users by various attributes.
  223. type ICQUserFinder interface {
  224. // FindByUIN returns the user with a matching UIN.
  225. FindByUIN(ctx context.Context, UIN uint32) (state.User, error)
  226. // FindByICQEmail returns the user with a matching ICQ-registered email address.
  227. FindByICQEmail(ctx context.Context, email string) (state.User, error)
  228. // FindByICQName returns users matching the given first name, last name, and/or nickname.
  229. // Empty values are ignored and not included in the search criteria.
  230. FindByICQName(ctx context.Context, firstName, lastName, nickName string) ([]state.User, error)
  231. // FindByICQInterests returns users who share at least one keyword under the
  232. // specified interest category code.
  233. FindByICQInterests(ctx context.Context, code uint16, keywords []string) ([]state.User, error)
  234. // FindByICQKeyword returns users who have the specified keyword in any interest category.
  235. FindByICQKeyword(ctx context.Context, keyword string) ([]state.User, error)
  236. // SearchICQUsers performs a flexible, AND-combined search over ICQ directory
  237. // fields (white pages).
  238. SearchICQUsers(ctx context.Context, c state.ICQUserSearchCriteria) ([]state.User, error)
  239. }
  240. // ICQUserUpdater defines methods for updating various fields of an ICQ user's profile.
  241. type ICQUserUpdater interface {
  242. // SetAffiliations updates the user's affiliations, such as memberships in organizations or groups.
  243. SetAffiliations(ctx context.Context, name state.IdentScreenName, data state.ICQAffiliations) error
  244. // SetBasicInfo updates the user's basic profile information.
  245. SetBasicInfo(ctx context.Context, name state.IdentScreenName, data state.ICQBasicInfo) error
  246. // SetInterests updates the user's interests.
  247. SetInterests(ctx context.Context, name state.IdentScreenName, data state.ICQInterests) error
  248. // SetMoreInfo updates additional personal details beyond the basic profile.
  249. SetMoreInfo(ctx context.Context, name state.IdentScreenName, data state.ICQMoreInfo) error
  250. // SetUserNotes updates the user's profile notes.
  251. SetUserNotes(ctx context.Context, name state.IdentScreenName, data state.ICQUserNotes) error
  252. // SetWorkInfo updates the user's professional or employment-related details.
  253. SetWorkInfo(ctx context.Context, name state.IdentScreenName, data state.ICQWorkInfo) error
  254. // SetPermissions updates the user's privacy and permission settings.
  255. SetPermissions(ctx context.Context, name state.IdentScreenName, data state.ICQPermissions) error
  256. // SetHomepageCategory updates the user's homepage category and keyword.
  257. SetHomepageCategory(ctx context.Context, name state.IdentScreenName, data state.ICQHomepageCategory) error
  258. // SetICQInfo updates all ICQ profile columns for the user in one operation.
  259. SetICQInfo(ctx context.Context, name state.IdentScreenName, info state.ICQInfo) error
  260. }
  261. // MessageRelayer defines methods for delivering SNAC messages to one or more
  262. // AIM screen names.
  263. type MessageRelayer interface {
  264. // RelayToScreenNames sends the given SNAC message to all specified screen names.
  265. RelayToScreenNames(ctx context.Context, screenNames []state.IdentScreenName, msg wire.SNACMessage)
  266. // RelayToScreenName sends the given SNAC message to a single screen name.
  267. RelayToScreenName(ctx context.Context, screenName state.IdentScreenName, msg wire.SNACMessage)
  268. // RelayToOtherInstances forwards a SNAC to other concurrent instances for the same user
  269. RelayToOtherInstances(ctx context.Context, instance *state.SessionInstance, msg wire.SNACMessage)
  270. // RelayToScreenNameActiveOnly sends the given SNAC message to active sessions (not away or idle) for a single screen name.
  271. RelayToScreenNameActiveOnly(ctx context.Context, screenName state.IdentScreenName, msg wire.SNACMessage)
  272. // RelayToSelf forwards a SNAC to the current session instance.
  273. RelayToSelf(ctx context.Context, instance *state.SessionInstance, msg wire.SNACMessage)
  274. }
  275. // OfflineMessageManager defines operations for managing offline messages.
  276. // These messages are stored temporarily when a recipient is unavailable,
  277. // and are retrieved once the recipient comes online. Offline messages are
  278. // available in all ICQ versions and AIM 6+.
  279. type OfflineMessageManager interface {
  280. // DeleteMessages removes all offline messages for the specified recipient.
  281. DeleteMessages(ctx context.Context, recip state.IdentScreenName) error
  282. // RetrieveMessages returns all offline messages for the specified recipient.
  283. RetrieveMessages(ctx context.Context, recip state.IdentScreenName) ([]state.OfflineMessage, error)
  284. // SaveMessage stores a new offline message for delivery when the recipient comes online and returns the sender's total queued message count for that recipient.
  285. SaveMessage(ctx context.Context, offlineMessage state.OfflineMessage) (int, error)
  286. // SetOfflineMsgCount sets the offline message count for a user.
  287. SetOfflineMsgCount(ctx context.Context, screenName state.IdentScreenName, count int) error
  288. }
  289. // ProfileManager defines methods for managing and querying AIM user profiles,
  290. // including directory information, interest keywords, and free-form profile content.
  291. type ProfileManager interface {
  292. // FindByAIMEmail returns the user with the given AIM-associated email address.
  293. FindByAIMEmail(ctx context.Context, email string) (state.User, error)
  294. // FindByAIMKeyword returns users who have the specified keyword in their profile interests.
  295. FindByAIMKeyword(ctx context.Context, keyword string) ([]state.User, error)
  296. // FindByAIMNameAndAddr returns users matching the specified name and address directory info.
  297. // Fields left empty in the input are ignored in the query.
  298. FindByAIMNameAndAddr(ctx context.Context, info state.AIMNameAndAddr) ([]state.User, error)
  299. // InterestList returns the list of available interest categories and keywords
  300. // that users can associate with their profiles.
  301. InterestList(ctx context.Context) ([]wire.ODirKeywordListItem, error)
  302. // Profile returns the user's profile information for the given screen name.
  303. Profile(ctx context.Context, screenName state.IdentScreenName) (state.UserProfile, error)
  304. // SetDirectoryInfo updates the user's directory listing with name, city, state, zip, and country info.
  305. SetDirectoryInfo(ctx context.Context, screenName state.IdentScreenName, info state.AIMNameAndAddr) error
  306. // SetKeywords sets up to five interest keywords for the user's profile.
  307. SetKeywords(ctx context.Context, screenName state.IdentScreenName, keywords [5]string) error
  308. // SetProfile sets the user's profile information.
  309. SetProfile(ctx context.Context, screenName state.IdentScreenName, profile state.UserProfile) error
  310. // User returns the full user record associated with the given screen name.
  311. User(ctx context.Context, screenName state.IdentScreenName) (*state.User, error)
  312. }
  313. // SessionRegistry defines methods for managing active user sessions.
  314. // It ensures that only one session is active per screen name at any given time.
  315. type SessionRegistry interface {
  316. // AddSession adds a new session to the pool, enforcing a one-session-per-screen-name policy.
  317. // If a session for the given screen name is already active, this call blocks until the active
  318. // session is removed via [SessionRegistry.RemoveSession] or the context is canceled.
  319. //
  320. // When multiple concurrent calls are made for the same screen name, only one will succeed;
  321. // the others will return an error once the context is done.
  322. // If doMultiSess is true, allows multiple sessions for the same screen name.
  323. AddSession(ctx context.Context, screenName state.DisplayScreenName, doMultiSess bool, cfg ...func(sess *state.Session)) (*state.SessionInstance, error)
  324. // RemoveSession removes the given session from the registry, allowing future sessions
  325. // to be created for the same screen name.
  326. RemoveSession(session *state.Session)
  327. }
  328. // SessionRetriever defines a method for retrieving an active session
  329. // associated with a given screen name.
  330. type SessionRetriever interface {
  331. // RetrieveSession returns the session associated with the given screen name,
  332. // or nil if no active session exists. Returns the Session object if there
  333. // are active instances with complete signon.
  334. RetrieveSession(screenName state.IdentScreenName) *state.Session
  335. }
  336. // UserManager defines methods for accessing and inserting AIM user records.
  337. type UserManager interface {
  338. // InsertUser inserts a new user into the system. Return state.ErrDupUser
  339. // if a user with the same screen name already exists.
  340. InsertUser(ctx context.Context, u state.User) error
  341. // User returns the user record associated with the given screen name.
  342. User(ctx context.Context, screenName state.IdentScreenName) (*state.User, error)
  343. // SetWarnLevel updates the last warn update time and warning level for a user.
  344. SetWarnLevel(ctx context.Context, user state.IdentScreenName, lastWarnUpdate time.Time, lastWarnLevel uint16) error
  345. }