func TestScope(t *testing.T) { Convey("Given a scope and a comparsion slice that matches it", t, func() { scope := []string{ "hello", "world", } what := []string{ "hello:world", "world", } Convey("InScope should return true", func() { So(models.InScope(scope, what), ShouldBeTrue) }) }) Convey("Given a scope and a comparsion slice that doesn't match it", t, func() { scope := []string{ "hello", "world", } what := []string{ "foobar", } Convey("InScope should return false", func() { So(models.InScope(scope, what), ShouldBeFalse) }) }) }
func (a *API) getAccountAddresses(c *gin.Context) { // Token and account from context var ( ownAccount = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the ID from the URL id := c.Param("id") if id == "me" { id = ownAccount.ID } // Check the scope if id == ownAccount.ID { if !models.InScope(token.Scope, []string{"addresses:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } // Get addresses from database cursor, err := r.Table("addresses").GetAllByIndex("owner", id).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var addresses []*models.Address if err := cursor.All(&addresses); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // Write the response c.JSON(200, addresses) return }
func (a *API) readResource(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the resource ID and fetch it from database id := c.Param("id") cursor, err := r.Table("resources").Get(id).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var resource *models.Resource if err := cursor.All(&resource); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } if resource.Owner == account.ID { // Check the scope if !models.InScope(token.Scope, []string{"resources:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { // Check the scope if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } c.JSON(200, resource) }
func (a *API) createResource(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Check the scope if !models.InScope(token.Scope, []string{"resources:create"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Decode the input var input struct { Meta map[string]interface{} `json:"meta"` Body []byte `json:"body"` Tags []string `json:"tags"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } // Save it into database resource := &models.Resource{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: account.ID, Meta: input.Meta, Body: input.Body, Tags: input.Tags, } // Insert it into database if err := r.Table("resources").Insert(resource).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } c.JSON(201, resource) }
func (a *API) getAccountResources(c *gin.Context) { // Token and account from context var ( ownAccount = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the ID from the URL id := c.Param("id") if id == "me" { id = ownAccount.ID } // Check the scope if id == ownAccount.ID { if !models.InScope(token.Scope, []string{"resources:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } // 1. Owner filter query := r.Table("resources").GetAllByIndex("owner", id) // 2. Tag filter if tagstr := c.Query("tags"); tagstr != "" { tags := strings.Split(tagstr, ",") // Cast to []interface{} tagsi := []interface{}{} for _, tag := range tags { tagsi = append(tagsi, tag) } query = query.Filter(func(row r.Term) r.Term { return row.Field("tags").Contains(tagsi...) }) } // 3. Meta filter // not needed right now // 4. Date created and date modified ts := func(field string) error { if dm := c.Query(field); dm != "" { dmp := strings.Split(dm, ",") if len(dmp) == 1 || dmp[1] == "" { // parse dmp[0] d0, err := time.Parse(time.RFC3339, dmp[0]) if err != nil { return err } // after dmp[0] query = query.Filter(func(row r.Term) r.Term { return row.Field(field).Ge(d0) }) } else { // parse dmp[1] d1, err := time.Parse(time.RFC3339, dmp[1]) if err != nil { return err } if dmp[0] == "" { // until dmp[1] query = query.Filter(func(row r.Term) r.Term { return row.Field(field).Le(d1) }) } else { // parse dmp[0] d0, err := time.Parse(time.RFC3339, dmp[0]) if err != nil { return err } // between dmp[0] and dmp[1] query = query.Filter(func(row r.Term) r.Term { return row.Field(field).Ge(d0).And(row.Field(field).Le(d1)) }) } } } return nil } if err := ts("date_modified"); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } if err := ts("date_created"); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // 5. Pluck / Without if pls := c.Query("pluck"); pls != "" { pl := strings.Split(pls, ",") // Cast to []interface{} pli := []interface{}{} for _, field := range pl { pli = append(pli, field) } query = query.Pluck(pli...) } else if wos := c.Query("wo"); wos != "" { wo := strings.Split(wos, ",") // Cast to []interface{} woi := []interface{}{} for _, field := range wo { woi = append(woi, field) } query = query.Pluck(woi...) } // 6. Ordering if obs := c.Query("order_by"); obs != "" { ob := strings.Split(obs, ",") fields := []interface{}{} for _, fi := range ob { asc := true if fi[0] == '-' { asc = false fi = fi[1:] } else if fi[0] == '+' || fi[0] == ' ' { fi = fi[1:] } field := r.Row.Field(fi) if path := strings.Split(fi, "."); len(path) > 1 { field = r.Row.Field(path[0]) for i := 1; i < len(path); i++ { field = field.Field(path[i]) } } if !asc { field = r.Desc(field) } fields = append(fields, field) } query = query.OrderBy(fields...) } // 7. Limiting var ( sks = c.Query("skip") lms = c.Query("limit") err error skip, limit int ) if sks != "" { skip, err = strconv.Atoi(sks) if err != nil { c.JSON(400, &gin.H{ "code": 0, "error": err.Error(), }) return } } if lms != "" { limit, err = strconv.Atoi(lms) if err != nil { c.JSON(400, &gin.H{ "code": 0, "error": err.Error(), }) return } } if skip != 0 && limit != 0 { query = query.Slice(skip, skip+limit) } else if skip == 0 && limit != 0 { query = query.Limit(limit) } else if skip != 0 && limit == 0 { query = query.Skip(skip) } // Get resources from database without bodies cursor, err := query.Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var resources []*models.Resource if err := cursor.All(&resources); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // Write the response c.JSON(200, resources) return }
func (a *API) createToken(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) currentToken = c.MustGet("token").(*models.Token) ) if !models.InScope(currentToken.Scope, []string{"tokens:oauth"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Decode the input var input struct { Type string `json:"type"` ClientID string `json:"client_id"` Scope []string `json:"scope"` ExpiryTime int64 `json:"expiry_time"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } // Run the validation errors := []string{} // Type has to be code if input.Type != "code" && input.Type != "auth" { errors = append(errors, "Only \"code\" token can be created using this endpoint.") } // Scope must contain proper scopes sm := map[string]struct{}{} for _, scope := range input.Scope { if _, ok := models.Scopes[scope]; !ok { errors = append(errors, "Scope \""+scope+"\" does not exist.") } else { sm[scope] = struct{}{} } } if _, ok := sm["password_grant"]; ok { errors = append(errors, "You can not request the password grant scope.") } if _, ok := sm["admin"]; ok && account.Subscription != "admin" { errors = append(errors, "You can not request the admin scope.") } // Expiry time must be valid if input.ExpiryTime == 0 { input.ExpiryTime = 86400 } else if input.ExpiryTime < 0 { errors = append(errors, "Invalid expiry time.") } // Client ID has to be an application ID var application *models.Application if input.ClientID == "" { errors = append(errors, "Client ID is missing.") } else { cursor, err := r.Table("applications").Get(input.ClientID).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() if err := cursor.One(&application); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } if application.ID == "" { errors = append(errors, "There is no such application.") } } // Abort the request if there are errors if len(errors) > 0 { c.JSON(422, &gin.H{ "code": 0, "message": "Validation failed.", "errors": errors, }) return } // Create a new token token := &models.Token{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: account.ID, ExpiryDate: time.Now().Add(time.Duration(input.ExpiryTime) * time.Second), Type: input.Type, Scope: input.Scope, ClientID: input.ClientID, } // Insert it into database if err := r.Table("tokens").Insert(token).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Write the token into the response c.JSON(201, token) return }
func (a *API) getAccountLabels(c *gin.Context) { // Token and account from context var ( ownAccount = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the ID from the URL id := c.Param("id") if id == "me" { id = ownAccount.ID } // Check the scope if id == ownAccount.ID { if !models.InScope(token.Scope, []string{"labels:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } // Get labels from database cursor, err := r.Table("labels").GetAllByIndex("owner", id).Map(func(label r.Term) r.Term { return label.Merge(map[string]interface{}{ "total_threads": r.Table("threads").GetAllByIndex("labels", label.Field("id")).Count(), "unread_threads": r.Table("threads").GetAllByIndex("labelsIsRead", []interface{}{ label.Field("id"), false, }).Count(), }) }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var labels []struct { models.Label TotalThreads int `json:"total_threads" gorethink:"total_threads"` UnreadThreads int `json:"unread_threads" gorethink:"unread_threads"` } if err := cursor.All(&labels); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // Write the response c.JSON(200, labels) return }
func (a *API) getLabelThreads(c *gin.Context) { // Token and account from context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the ID from the URL id := c.Param("id") // Get label from the database cursor, err := r.Table("labels").Get(id).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } var label *models.Label if err := cursor.One(&label); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // Check the ownership and scope if label.Owner == account.ID { if !models.InScope(token.Scope, []string{"labels:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } // Get threads from the database cursor, err = r.Table("threads").GetAllByIndex("labels", label.ID).OrderBy(r.Desc("date_modified")).Map(func(thread r.Term) r.Term { return thread.Merge(map[string]interface{}{ "manifest": r.Table("emails").GetAllByIndex("thread", thread.Field("id")).OrderBy("date_modified").CoerceTo("array"), }).Do(func(thread r.Term) r.Term { return r.Branch( thread.Field("manifest").Count().Gt(0), thread.Merge(map[string]interface{}{ "manifest": thread.Field("manifest").Nth(0).Field("manifest"), }), thread.Without("manifest"), ) }) }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } var threads []*extendedThread if err := cursor.All(&threads); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } if threads == nil { threads = []*extendedThread{} } // Write the response c.JSON(200, threads) return }
func (a *API) updateAccount(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Decode the input var input struct { MainAddress string `json:"main_address"` NewPassword []byte `json:"new_password"` OldPassword []byte `json:"old_password"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } var newAddress *models.Address if input.MainAddress != "" { // Fetch address from the database cursor, err := r.Table("addresses").Get(input.MainAddress).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() if err := cursor.One(&newAddress); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Verify that we got something if newAddress.ID == "" { c.JSON(422, &gin.H{ "code": 0, "message": "No such address exists", }) return } } // Resolve the account ID and check scope accordingly id := c.Param("id") if id == "me" { // Swap the ID to own account's ID id = account.ID } if id == account.ID { // Check the scope if !models.InScope(token.Scope, []string{"account:modify"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Validate password valid, _, err := account.VerifyPassword(input.OldPassword) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } if !valid { c.JSON(401, &gin.H{ "code": 0, "message": "Invalid password", }) return } } else { // Check the scope if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Fetch the account from the database cursor, err := r.Table("accounts").Get(id).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() if err := cursor.One(&account); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Ensure that we got something if account.ID == "" { c.JSON(404, &gin.H{ "code": 0, "error": "Account not found", }) return } } // Apply change to the password if input.NewPassword != nil && len(input.NewPassword) != 0 { if len(input.NewPassword) == 32 { c.JSON(422, &gin.H{ "code": 0, "message": "Invalid new password format", }) return } if err := account.SetPassword(input.NewPassword); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } } // Apply change to the main address setting if newAddress.ID != "" { // Check address ownership if newAddress.Owner != account.ID { c.JSON(422, &gin.H{ "code": 0, "message": "User does not own that address", }) return } account.MainAddress = newAddress.ID } // Perform the update if err := r.Table("accounts").Update(account).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Write the response account.Password = nil c.JSON(200, account) }
func (a *API) readAccount(c *gin.Context) { // Get token and account info from the context var ( ownAccount = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the account ID and check scope accordingly id := c.Param("id") if id == "me" { // Swap the ID to own account's ID id = ownAccount.ID } if id == ownAccount.ID { // Check the scope if !models.InScope(token.Scope, []string{"account:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Get addresses from database cursor, err := r.Table("addresses").GetAllByIndex("owner", id).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var addresses []*models.Address if err := cursor.All(&addresses); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } ownAccount.Password = nil // Write the response c.JSON(200, struct { *models.Account Addresses []*models.Address `json:"addresses"` }{ Account: ownAccount, Addresses: addresses, }) return } // Check the scope if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Fetch the account cursor, err := r.Table("accounts").Get(id).Do(func(account r.Term) r.Term { return r.Branch( account.Eq(nil), map[string]interface{}{}, account.Without("password").Merge(map[string]interface{}{ "addresses": r.Table("addresses").GetAllByIndex("owner", account.Field("id")), }), ) }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var result struct { models.Account Addresses []*models.Address `json:"addresses"` } if err := cursor.One(&result); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } if result.ID == "" { c.JSON(404, &gin.H{ "code": 0, "error": "Account not found", }) return } c.JSON(200, result) }
func (a *API) createKey(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Check the scope if !models.InScope(token.Scope, []string{"keys"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Decode the input var input struct { Body []byte `json:"body"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } // Validate the input if input.Body == nil || len(input.Body) == 0 { c.JSON(422, &gin.H{ "code": 0, "message": "No key body in the input", }) return } // Parse the key keyring, err := openpgp.ReadKeyRing(bytes.NewReader(input.Body)) if err != nil { c.JSON(422, &gin.H{ "code": 0, "message": "Invalid key format", }) return } publicKey := keyring[0].PrimaryKey // Parse the identities identities := []*models.Identity{} for _, identity := range keyring[0].Identities { id := &models.Identity{ Name: identity.Name, } if identity.SelfSignature != nil { sig := identity.SelfSignature id.SelfSignature = &models.Signature{ Type: uint8(sig.SigType), Algorithm: uint8(sig.PubKeyAlgo), Hash: uint(sig.Hash), CreationTime: sig.CreationTime, SigLifetimeSecs: sig.SigLifetimeSecs, KeyLifetimeSecs: sig.KeyLifetimeSecs, IssuerKeyID: sig.IssuerKeyId, IsPrimaryID: sig.IsPrimaryId, RevocationReason: sig.RevocationReason, RevocationReasonText: sig.RevocationReasonText, } } if identity.Signatures != nil { id.Signatures = []*models.Signature{} for _, sig := range identity.Signatures { id.Signatures = append(id.Signatures, &models.Signature{ Type: uint8(sig.SigType), Algorithm: uint8(sig.PubKeyAlgo), Hash: uint(sig.Hash), CreationTime: sig.CreationTime, SigLifetimeSecs: sig.SigLifetimeSecs, KeyLifetimeSecs: sig.KeyLifetimeSecs, IssuerKeyID: sig.IssuerKeyId, IsPrimaryID: sig.IsPrimaryId, RevocationReason: sig.RevocationReason, RevocationReasonText: sig.RevocationReasonText, }) } } identities = append(identities, id) } // Acquire key's length length, err := publicKey.BitLength() if err != nil { c.JSON(422, &gin.H{ "code": 0, "message": "Couldn't calculate bit length", }) return } // Generate a new key struct key := &models.Key{ ID: hex.EncodeToString(publicKey.Fingerprint[:]), DateCreated: time.Now(), DateModified: time.Now(), Owner: account.ID, Algorithm: uint8(publicKey.PubKeyAlgo), Length: length, Body: input.Body, KeyID: publicKey.KeyId, KeyIDString: publicKey.KeyIdString(), KeyIDShortString: publicKey.KeyIdShortString(), Identities: identities, } // Insert it into database if err := r.Table("keys").Insert(key).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } c.JSON(201, key) }