Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

275 lines
6.7KB

  1. package main
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "log"
  8. "math/rand"
  9. "net/http"
  10. "net/url"
  11. "strconv"
  12. "strings"
  13. "time"
  14. )
  15. var cookLeadID = "biukop_cl"
  16. func crmpixelImage() (pixel []byte, err error) {
  17. b64pixel := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QcIBzEJ3JY/6wAAAAh0RVh0Q29tbWVudAD2zJa/AAAADUlEQVQI12NgYGBgAAAABQABXvMqOgAAAABJRU5ErkJggg=="
  18. pixel, err = base64.StdEncoding.DecodeString(b64pixel)
  19. return
  20. }
  21. func crmpixel(w http.ResponseWriter, r *http.Request) {
  22. cookie := crmpixelCookie(r)
  23. http.SetCookie(w, &cookie)
  24. //send out pixel 1x1 transparent png file
  25. pixel, err := crmpixelImage()
  26. if err == nil {
  27. w.Write(pixel)
  28. } else {
  29. fmt.Fprint(w, "decodig pixel image wrong")
  30. }
  31. }
  32. func crmpixelCookie(r *http.Request) (ret http.Cookie) {
  33. cookie, leadok := r.Cookie(cookLeadID) //crm lead
  34. if leadok != nil {
  35. ret, _ = createNewCookie(r)
  36. return
  37. }
  38. valid := isValidCookieBiukopCL(cookie.Value)
  39. if !valid {
  40. ret, _ = createNewCookie(r)
  41. return
  42. }
  43. //refresh expiration
  44. ret = *cookie
  45. ret.Expires = time.Now().Add(10 * 365 * 24 * time.Hour)
  46. return
  47. }
  48. func getLeadIDFromCookie(r *http.Request) (leadID string, ok bool) {
  49. return cookieVerifyAndGet(r, cookLeadID)
  50. }
  51. func cookieVerifyAndGet(r *http.Request, name string) (value string, ok bool) {
  52. ok = false
  53. cookie, leadok := r.Cookie(name)
  54. if leadok != nil {
  55. return
  56. }
  57. valid := isValidCookieBiukopCL(cookie.Value)
  58. if !valid {
  59. return
  60. }
  61. //correctly split string
  62. s := strings.Split(cookie.Value, "|")
  63. if len(s) != 4 || s[0] == "" {
  64. return
  65. }
  66. ok = true
  67. value = s[0]
  68. return
  69. }
  70. func createNewCookie(r *http.Request) (ret http.Cookie, info crmdLead) {
  71. info = crmCreateNewAnonymousLeadByHTTPRequest(r)
  72. ret = cookieFromLeadID(info.ID)
  73. return
  74. }
  75. func cookieFromLeadID(leadID string) (ret http.Cookie) {
  76. return cookieCreateLongTerm(cookLeadID, leadID)
  77. }
  78. func cookieCreateLongTerm(name, value string) (ret http.Cookie) {
  79. expiration := time.Now().Add(10 * 365 * 24 * time.Hour)
  80. signedValue := cookieSignValue(value)
  81. ret = http.Cookie{Name: name, Value: signedValue, Expires: expiration}
  82. return
  83. }
  84. func cookieCreate(name, value string, expireInSeconds int) (ret http.Cookie) {
  85. expiration := time.Now().Add(time.Duration(expireInSeconds) * time.Second)
  86. signedValue := cookieSignValue(value)
  87. ret = http.Cookie{Name: name, Value: signedValue, Expires: expiration}
  88. return
  89. }
  90. //set to "" and make it expire
  91. func cookieClear(name string) (ret http.Cookie) {
  92. expiration := time.Unix(0, 0) //Epoc
  93. ret = http.Cookie{Name: name, Value: "", Expires: expiration}
  94. return
  95. }
  96. func crmCreateNewAnonymousLeadByHTTPRequest(r *http.Request) (info crmdLead) {
  97. info.FirstName = "Anonymous"
  98. info.LastName = "User " + r.RemoteAddr
  99. info.Password = "Password"
  100. info.Status = "Anonymous"
  101. info.Description = "Anonymous user from " + r.RemoteAddr
  102. info.ForceDuplicate = true
  103. b, err := json.Marshal(info)
  104. if err != nil {
  105. return
  106. }
  107. entity, err := crmCreateEntity("Lead", b)
  108. if err == nil || isErrIndicateDuplicate(err) {
  109. info, _ = entity.(crmdLead)
  110. }
  111. return
  112. }
  113. func isValidCookieBiukopCL(cookieValue string) (valid bool) {
  114. valid = false
  115. //correctly split string
  116. s := strings.Split(cookieValue, "|")
  117. if len(s) != 4 || s[0] == "" {
  118. return
  119. }
  120. id, nonce, timestamp, signature := s[0], s[1], s[2], s[3]
  121. //check signature
  122. combined := id + nonce
  123. expected := calculateSignature(timestamp, combined, IntraAPIConfig.CRMSecrete)
  124. if expected != signature {
  125. return
  126. }
  127. ts, err := strconv.Atoi(timestamp)
  128. if err != nil {
  129. return
  130. }
  131. if timestampOldThan(int32(ts), 86400) { //older than 1 day
  132. //find lead data
  133. _, err := crmpixelLead(id)
  134. if err != nil {
  135. return
  136. }
  137. }
  138. valid = true
  139. return
  140. }
  141. func buildBiukopCLsignature(id, nonce string) (timestamp, signature string) {
  142. combined := id + nonce
  143. timestamp = fmt.Sprintf("%d", time.Now().Unix())
  144. signature = calculateSignature(timestamp, combined, IntraAPIConfig.CRMSecrete)
  145. return
  146. }
  147. func cookieSignValue(id string) (ret string) {
  148. rand.Seed(time.Now().Unix())
  149. nonce := fmt.Sprintf("%d", rand.Intn(655352017))
  150. timestamp, signature := buildBiukopCLsignature(id, nonce)
  151. strs := []string{id, nonce, timestamp, signature} //order is very important
  152. ret = strings.Join(strs, "|")
  153. return
  154. }
  155. func crmpixelLead(id string) (info crmdLead, err error) {
  156. entity, err := crmGetEntity("Lead", id)
  157. if err != nil {
  158. return
  159. }
  160. info, ok := entity.(crmdLead)
  161. if !ok {
  162. err = errors.New("search lead, return a bad entity type")
  163. }
  164. return
  165. }
  166. //for user's initial registration, especially for wechat users
  167. //they visit a url that is specifically designed for them to
  168. //auth and input their profile data.
  169. //the url's query string will contains a token and a signature
  170. //so that it's verified, by single get request, to allow people to
  171. //enter their details into the CRM system.
  172. //
  173. //this handler, check's the query sting ,set an auth cookie to the client
  174. //and serve angular app, through an URL "/profile/edit"
  175. //or if the user has already been registered,
  176. //redirect user to a URL "/pages/dashboard"
  177. //
  178. func setTrackingCookieAndRecirect(w http.ResponseWriter, r *http.Request) {
  179. m, err := url.ParseQuery(r.URL.RawQuery)
  180. if err != nil {
  181. response400Handler(w)
  182. return
  183. }
  184. url, ok := m["url"]
  185. if !ok {
  186. response400Handler(w)
  187. return
  188. }
  189. //check request leadID - lid and my cookie lead id
  190. leadID, ok := m["lid"]
  191. myLeadID, found := getLeadIDFromCookie(r) //existing cookie
  192. expireInSeconds := 120 //default 2minute
  193. if found && ok && myLeadID == leadID[0] { //our cookie match the request lid
  194. expireInSeconds = 86400 //extended to 1 day
  195. }
  196. //check signature, prevent unauthorized request
  197. if !checkSignatureByTokenWithExpireSeconds(r, IntraAPIConfig.CRMSecrete, expireInSeconds) {
  198. response403Handler(w)
  199. return
  200. }
  201. //set cookie if any
  202. if ok {
  203. log.Println("setlead cookie :" + leadID[0])
  204. cookie := cookieFromLeadID(leadID[0])
  205. log.Println(cookie)
  206. http.SetCookie(w, &cookie)
  207. } else {
  208. cookie := crmpixelCookie(r)
  209. http.SetCookie(w, &cookie)
  210. }
  211. //get expire settings if any
  212. expire := 7200 //2 hours
  213. expireTime, ok := m["expire"]
  214. if ok {
  215. expire, _ = strconv.Atoi(expireTime[0])
  216. }
  217. //set all cookie from url
  218. for k, v := range m {
  219. if k == "lid" || k == "url" || k == "expire" || k == "nonce" || k == "signature" || k == "timestamp" { //skip lead id and URL and expire
  220. continue
  221. }
  222. if v[0] != "" {
  223. log.Printf("set cookie %s=%s", k, v)
  224. cookie := cookieCreate(k, v[0], expire)
  225. http.SetCookie(w, &cookie)
  226. } else { //clear cook
  227. log.Printf("clear cookie %s=%s", k, v)
  228. cookie := cookieClear(k)
  229. http.SetCookie(w, &cookie)
  230. }
  231. }
  232. //perform redirect
  233. http.Redirect(w, r, url[0], 307) //302 temp redirect
  234. return
  235. }