PageRenderTime 2059ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/src/canopy/datalayer/cassandra_datalayer/cass_connection.go

https://github.com/canopy-project/canopy-cloud
Go | 321 lines | 252 code | 44 blank | 25 comment | 63 complexity | 7c378020892a467796581a9c753f706c MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * Copyright 2014 Gregory Prisament
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package cassandra_datalayer
  17. import(
  18. "canopy/canolog"
  19. "canopy/datalayer"
  20. "canopy/sddl"
  21. "canopy/util/random"
  22. "fmt"
  23. "github.com/gocql/gocql"
  24. "code.google.com/p/go.crypto/bcrypt"
  25. "regexp"
  26. "strings"
  27. "time"
  28. )
  29. type CassConnection struct {
  30. dl *CassDatalayer
  31. session *gocql.Session
  32. }
  33. // Use with care. Erases all sensor data.
  34. func (conn *CassConnection) ClearSensorData() {
  35. tables := []string{
  36. "propval_int",
  37. "propval_float",
  38. "propval_double",
  39. "propval_timestamp",
  40. "propval_boolean",
  41. "propval_void",
  42. "propval_string",
  43. }
  44. for _, table := range tables {
  45. err := conn.session.Query(`TRUNCATE ` + table).Exec();
  46. if (err != nil) {
  47. canolog.Error("Error truncating ", table, ":", err)
  48. }
  49. }
  50. }
  51. func (conn *CassConnection) Close() {
  52. conn.session.Close()
  53. }
  54. func validateUsername(username string) error {
  55. if username == "leela" {
  56. return fmt.Errorf("Username reserved")
  57. }
  58. if len(username) < 5 {
  59. return fmt.Errorf("Username too short")
  60. }
  61. if len(username) > 24 {
  62. return fmt.Errorf("Username too long")
  63. }
  64. matched, err := regexp.MatchString("[a-zA-Z][a-zA-Z0-9_]+", username)
  65. if !matched || err != nil {
  66. return fmt.Errorf("Invalid characters in username")
  67. }
  68. return nil
  69. }
  70. func validatePassword(password string) error {
  71. if len(password) < 6 {
  72. return fmt.Errorf("Password too short")
  73. }
  74. if len(password) > 120 {
  75. return fmt.Errorf("Password too long")
  76. }
  77. return nil
  78. }
  79. func validateEmail(email string) error {
  80. // TODO
  81. return nil
  82. }
  83. func (conn *CassConnection) CreateAccount(username, email, password string) (datalayer.Account, error) {
  84. password_hash, _ := bcrypt.GenerateFromPassword([]byte(password + salt), hashCost)
  85. err := validateUsername(username)
  86. if err != nil {
  87. return nil, err
  88. }
  89. err = validateEmail(email)
  90. if err != nil {
  91. return nil, err
  92. }
  93. err = validatePassword(password)
  94. if err != nil {
  95. return nil, err
  96. }
  97. activation_code, err := random.Base64String(24)
  98. if err != nil {
  99. return nil, err
  100. }
  101. // TODO: transactionize
  102. if err := conn.session.Query(`
  103. INSERT INTO accounts (username, email, password_hash, activated, activation_code)
  104. VALUES (?, ?, ?, ?, ?)
  105. `, username, email, password_hash, false, activation_code).Exec(); err != nil {
  106. canolog.Error("Error creating account:", err)
  107. return nil, err
  108. }
  109. if err := conn.session.Query(`
  110. INSERT INTO account_emails (email, username)
  111. VALUES (?, ?)
  112. `, email, username).Exec(); err != nil {
  113. canolog.Error("Error setting account email:", err)
  114. return nil, err
  115. }
  116. return &CassAccount{conn, username, email, password_hash, false, activation_code}, nil
  117. }
  118. func (conn *CassConnection) CreateDevice(name string, uuid *gocql.UUID, secretKey string, publicAccessLevel datalayer.AccessLevel) (datalayer.Device, error) {
  119. // TODO: validate parameters
  120. var id gocql.UUID
  121. var err error
  122. if uuid == nil {
  123. id, err = gocql.RandomUUID()
  124. if err != nil {
  125. return nil, err
  126. }
  127. } else {
  128. id = *uuid
  129. }
  130. if secretKey == "" {
  131. secretKey, err = random.Base64String(24)
  132. if err != nil {
  133. return nil, err
  134. }
  135. }
  136. err = conn.session.Query(`
  137. INSERT INTO devices (device_id, secret_key, friendly_name, public_access_level)
  138. VALUES (?, ?, ?, ?)
  139. `, id, secretKey, name, publicAccessLevel).Exec()
  140. if err != nil {
  141. canolog.Error("Error creating device:", err)
  142. return nil, err
  143. }
  144. return &CassDevice{
  145. conn: conn,
  146. deviceId: id,
  147. secretKey: secretKey,
  148. name: name,
  149. doc: sddl.Sys.NewEmptyDocument(),
  150. docString: "",
  151. publicAccessLevel: publicAccessLevel,
  152. }, nil
  153. }
  154. func (conn *CassConnection) LookupOrCreateDevice(deviceId gocql.UUID, publicAccessLevel datalayer.AccessLevel) (datalayer.Device, error) {
  155. // TODO: improve this implementation.
  156. // Fix race conditions?
  157. // Fix error paths?
  158. device, err := conn.LookupDevice(deviceId)
  159. if device != nil {
  160. canolog.Info("LookupOrCreateDevice - device ", deviceId, " found")
  161. return device, nil
  162. }
  163. device, err = conn.CreateDevice("AnonDevice", &deviceId, "", publicAccessLevel)
  164. if err != nil {
  165. canolog.Info("LookupOrCreateDevice - device ", deviceId, "error")
  166. }
  167. canolog.Info("LookupOrCreateDevice - device ", deviceId, " created")
  168. return device, err
  169. }
  170. func (conn *CassConnection) DeleteAccount(username string) {
  171. account, _ := conn.LookupAccount(username)
  172. email := account.Email()
  173. if err := conn.session.Query(`
  174. DELETE FROM accounts
  175. WHERE username = ?
  176. `, username).Exec(); err != nil {
  177. canolog.Error("Error deleting account", err)
  178. }
  179. if err := conn.session.Query(`
  180. DELETE FROM account_emails
  181. WHERE email = ?
  182. `, email).Exec(); err != nil {
  183. canolog.Error("Error deleting account email", err)
  184. }
  185. }
  186. func (conn *CassConnection) LookupAccount(usernameOrEmail string) (datalayer.Account, error) {
  187. var account CassAccount
  188. var username string
  189. canolog.Info("Looking up account: ", usernameOrEmail)
  190. if strings.Contains(usernameOrEmail, "@") {
  191. canolog.Info("It is an email address")
  192. // email address provided. Lookup username based on email
  193. err := conn.session.Query(`
  194. SELECT email, username FROM account_emails
  195. WHERE email = ?
  196. LIMIT 1
  197. `, usernameOrEmail).Consistency(gocql.One).Scan(
  198. &account.email, &username);
  199. if (err != nil) {
  200. canolog.Error("Error looking up account", err)
  201. return nil, err
  202. }
  203. } else {
  204. canolog.Info("It is not an email address")
  205. username = usernameOrEmail
  206. }
  207. canolog.Info("fetching info for: ", username)
  208. // Lookup account info based on username
  209. err := conn.session.Query(`
  210. SELECT username, email, password_hash, activated, activation_code FROM accounts
  211. WHERE username = ?
  212. LIMIT 1
  213. `, username).Consistency(gocql.One).Scan(
  214. &account.username, &account.email, &account.password_hash, &account.activated, &account.activation_code)
  215. if (err != nil) {
  216. canolog.Error("Error looking up account", err)
  217. return nil, err
  218. }
  219. canolog.Info("Success")
  220. account.conn = conn
  221. return &account, nil
  222. }
  223. func (conn *CassConnection)LookupAccountVerifyPassword(usernameOrEmail string, password string) (datalayer.Account, error) {
  224. account, err := conn.LookupAccount(usernameOrEmail)
  225. if err != nil {
  226. return nil, err
  227. }
  228. verified := account.VerifyPassword(password)
  229. if (!verified) {
  230. canolog.Info("Incorrect password for ", usernameOrEmail)
  231. return nil, datalayer.InvalidPasswordError
  232. }
  233. return account, nil
  234. }
  235. func (conn *CassConnection) LookupDevice(deviceId gocql.UUID) (datalayer.Device, error) {
  236. var device CassDevice
  237. device.deviceId = deviceId
  238. device.conn = conn
  239. var last_seen time.Time;
  240. err := conn.session.Query(`
  241. SELECT friendly_name, secret_key, sddl, last_seen
  242. FROM devices
  243. WHERE device_id = ?
  244. LIMIT 1`, deviceId).Consistency(gocql.One).Scan(
  245. &device.name,
  246. &device.secretKey,
  247. &device.docString,
  248. &last_seen)
  249. if err != nil {
  250. canolog.Error(err)
  251. return nil, err
  252. }
  253. // This scan returns Jan 1, 1970 UTC if last_seen is NULL.
  254. if last_seen.Before(time.Unix(1, 0)) {
  255. device.last_seen = nil
  256. } else {
  257. device.last_seen = &last_seen
  258. }
  259. if device.docString != "" {
  260. device.doc, err = sddl.Sys.ParseDocumentString(device.docString)
  261. if err != nil {
  262. canolog.Error("Error parsing class string for device: ", device.docString, err)
  263. return nil, err
  264. }
  265. } else {
  266. device.doc = sddl.Sys.NewEmptyDocument()
  267. }
  268. return &device, nil
  269. }
  270. func (conn *CassConnection) LookupDeviceByStringID(id string) (datalayer.Device, error) {
  271. deviceId, err := gocql.ParseUUID(id)
  272. if err != nil {
  273. canolog.Error(err)
  274. return nil, err
  275. }
  276. return conn.LookupDevice(deviceId)
  277. }