package main import ( "crypto/md5" "crypto/sha256" "database/sql" "encoding/json" "fmt" "image" "image/png" "mime/multipart" "os" "strconv" "strings" "time" "net/http" "github.com/gin-gonic/gin" "github.com/nfnt/resize" "zxq.co/ripple/rippleapi/common" "zxq.co/x/rs" ) func createAPIToken(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { resp403(c) return } sess := getSession(c) defer func() { sess.Save() c.Redirect(302, "/dev/tokens") }() if ok, _ := CSRF.Validate(ctx.User.ID, c.PostForm("csrf")); !ok { addMessage(c, errorMessage{T(c, "Your session has expired. Please try redoing what you were trying to do.")}) return } privileges := common.Privileges(common.Int(c.PostForm("privileges"))).CanOnly(ctx.User.Privileges) description := c.PostForm("description") var ( tokenStr string tokenMD5 string ) for { tokenStr = common.RandomString(32) tokenMD5 = fmt.Sprintf("%x", md5.Sum([]byte(tokenStr))) var id int err := db.QueryRow("SELECT id FROM tokens WHERE token = ? LIMIT 1", tokenMD5).Scan(&id) if err == sql.ErrNoRows { break } if err != nil { c.Error(err) resp500(c) return } } _, err := db.Exec("INSERT INTO tokens(user, privileges, description, token, private, last_updated) VALUES (?, ?, ?, ?, '0', ?)", ctx.User.ID, privileges, description, tokenMD5, time.Now().Unix()) if err != nil { c.Error(err) resp500(c) return } addMessage(c, successMessage{ fmt.Sprintf("Your token has been created successfully! Your token is: %s.
Keep it safe, don't show it around, and store it now! We won't show it to you again.", tokenStr), }) } func deleteAPIToken(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { resp403(c) return } sess := getSession(c) defer func() { sess.Save() c.Redirect(302, "/dev/tokens") }() if ok, _ := CSRF.Validate(ctx.User.ID, c.PostForm("csrf")); !ok { addMessage(c, errorMessage{T(c, "Your session has expired. Please try redoing what you were trying to do.")}) return } db.Exec("DELETE FROM tokens WHERE id = ? AND user = ? AND private = 0", c.PostForm("id"), ctx.User.ID) addMessage(c, successMessage{"That token has been deleted successfully."}) } func editAPIToken(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { resp403(c) return } sess := getSession(c) defer func() { sess.Save() c.Redirect(302, "/dev/tokens/edit?id="+c.PostForm("id")) }() if ok, _ := CSRF.Validate(ctx.User.ID, c.PostForm("csrf")); !ok { addMessage(c, errorMessage{T(c, "Your session has expired. Please try redoing what you were trying to do.")}) return } privileges := common.Privileges(common.Int(c.PostForm("privileges"))).CanOnly(ctx.User.Privileges) description := c.PostForm("description") _, err := db.Exec("UPDATE tokens SET privileges = ?, description = ? WHERE user = ? AND id = ? AND private = 0", privileges, description, ctx.User.ID, c.PostForm("id")) if err != nil { c.Error(err) resp500(c) return } addMessage(c, successMessage{ "Your token has been edited successfully!", }) } type oAuthClient struct { ID string Extra string RedirectURI string } // Name retrieves the name of an oAuthClient func (o oAuthClient) Name() string { var x [3]string json.Unmarshal([]byte(o.Extra), &x) return x[0] } // Avatar retrieves the avatar of an oAuthClient func (o oAuthClient) Avatar() string { var x [3]string json.Unmarshal([]byte(o.Extra), &x) return x[2] } // Owner retrieves the ID of the owner of an oAuthClient func (o oAuthClient) Owner() int { var x [3]string json.Unmarshal([]byte(o.Extra), &x) u, _ := strconv.Atoi(x[1]) return u } func getOAuthApplications(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { resp403(c) return } var apps []oAuthClient err := db.Select(&apps, `SELECT c.id, c.extra, c.redirect_uri as redirecturi FROM osin_client_user cu INNER JOIN osin_client c ON c.id = cu.client_id WHERE cu.user = ?`, ctx.User.ID) if err != nil { c.Error(err) resp500(c) return } simple(c, getSimpleByFilename("dev/apps.html"), nil, map[string]interface{}{ "apps": apps, }) } func editOAuthApplication(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { resp403(c) return } app := new(oAuthClient) if c.Query("id") != "new" { err := db.Get(app, `SELECT c.id, c.extra, c.redirect_uri as redirecturi FROM osin_client_user cu INNER JOIN osin_client c ON c.id = cu.client_id WHERE cu.user = ? AND cu.client_id = ?`, ctx.User.ID, c.Query("id")) switch err { case nil: break case sql.ErrNoRows: app = nil default: c.Error(err) resp500(c) return } } else { app = new(oAuthClient) } tpl := getSimpleByFilename("dev/edit_app.html") if c.Query("id") == "new" { tpl.TitleBar = "Create OAuth 2 application" } simple(c, tpl, nil, map[string]interface{}{ "app": app, }) } func editOAuthApplicationSubmit(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { resp403(c) return } c.Request.ParseMultipartForm(32 << 10) id := c.PostForm("id") sess := getSession(c) defer func() { sess.Save() c.Redirect(302, "/dev/apps/edit?id="+id) }() var avatarFilename string if id != "new" { var previousExtra string db.Get(&previousExtra, "SELECT extra FROM osin_client WHERE id = ?", id) oClient := oAuthClient{Extra: previousExtra} if previousExtra == "" || oClient.Owner() != ctx.User.ID { fmt.Println(previousExtra, oClient.Owner(), ctx.User.ID) // user will be returned to /dev/apps/edit?id=whatever, // which will say the token could not be found. return } avatarFilename = oClient.Avatar() } avatar, _, err := c.Request.FormFile("avatar") if err != nil && err != http.ErrMissingFile { c.Error(err) resp500(c) return } if avatar != nil { if avatarFilename != "" { err := deletePreviousAvatar(avatarFilename) if err != nil { c.Error(err) resp500(c) return } } avatarFilename, err = storeNewAvatar(avatar) if err != nil { c.Error(err) resp500(c) return } } name := common.SanitiseString(c.PostForm("name")) if len(name) > 25 { name = name[:25] } redirectURI := common.SanitiseString(c.PostForm("redirect_uri")) extra, _ := json.Marshal([3]string{ name, strconv.Itoa(ctx.User.ID), avatarFilename, }) if id == "new" { id = rs.StringFromChars(32, oAuthRandomChars) secret := rs.StringFromChars(64, oAuthRandomChars) secretSha := fmt.Sprintf("%x", sha256.Sum256([]byte(secret))) db.Exec("INSERT INTO osin_client(id, secret, extra, redirect_uri) VALUES (?, ?, ?, ?)", id, secretSha, string(extra), redirectURI) db.Exec("INSERT INTO osin_client_user(client_id, user) VALUES (?, ?)", id, ctx.User.ID) addMessage(c, successMessage{fmt.Sprintf(createClientMessage, id, secret)}) } else { db.Exec("UPDATE osin_client SET extra = ?, redirect_uri = ? WHERE id = ?", string(extra), redirectURI, id) addMessage(c, successMessage{"Your application has been saved."}) } } const createClientMessage = ` You can now get going integrating Ripple in your super cool project! Here's what you need:
client_id     = "%s"
client_secret = "%s"
As always: keep it safe, don't show it around, and store it now! We won't show you the client_secret again.` func deletePreviousAvatar(s string) error { return os.Remove("static/oauth-apps/" + s) } const oAuthRandomChars = "qwertyuiopasdfghjklzxcvbnm" func storeNewAvatar(file multipart.File) (string, error) { img, _, err := image.Decode(file) if err != nil { return "", err } img = resize.Thumbnail(256, 256, img, resize.Bilinear) newFilename := rs.StringFromChars(32, oAuthRandomChars) + ".png" f, err := os.Create(fmt.Sprintf("static/oauth-apps/%s", newFilename)) defer f.Close() if err != nil { return "", err } err = png.Encode(f, img) return newFilename, err } func deleteOAuthApplication(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { resp403(c) return } sess := getSession(c) defer func() { sess.Save() c.Redirect(302, "/dev/apps") }() if ok, _ := CSRF.Validate(ctx.User.ID, c.PostForm("csrf")); !ok { addMessage(c, errorMessage{T(c, "Your session has expired. Please try redoing what you were trying to do.")}) return } var x oAuthClient db.Get(&x, "SELECT extra FROM osin_client WHERE id = ?", c.PostForm("id")) if x.Owner() != ctx.User.ID { addMessage(c, errorMessage{"y u do dis"}) return } if x.Avatar() != "" { os.Remove("static/oauth-apps/" + x.Avatar()) } clientID := c.PostForm("id") db.Exec("DELETE FROM osin_access WHERE client = ?", clientID) db.Exec("DELETE FROM osin_client_user WHERE client_id = ?", clientID) db.Exec("DELETE FROM osin_client WHERE id = ?", clientID) addMessage(c, successMessage{"poof"}) } type authorization struct { oAuthClient Scope string CreatedAt time.Time Client string } var scopeMap = map[string]string{ "identify": "Identify", "read_confidential": "Read private information", "write": "Write", } func (a authorization) Scopes(c *gin.Context) string { if a.Scope == "" { return T(c, "Identify") } scopes := strings.Split(a.Scope, " ") scopes = append([]string{"identify"}, scopes...) for i, val := range scopes { scopes[i] = T(c, scopeMap[val]) } return strings.Join(scopes, ", ") } func authorizedApplications(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { resp403(c) } var apps []authorization err := db.Select(&apps, ` SELECT c.extra, a.scope, a.created_at AS createdat, a.client FROM osin_access a INNER JOIN osin_client c ON c.id = a.client WHERE a.extra = ? GROUP BY a.client ORDER BY a.created_at DESC`, ctx.User.ID) if err != nil { c.Error(err) resp500(c) return } simple( c, getSimpleByFilename("settings/authorized_applications.html"), nil, map[string]interface{}{ "apps": apps, }, ) } func revokeAuthorization(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { resp403(c) return } sess := getSession(c) defer func() { sess.Save() c.Redirect(302, "/settings/authorized_applications") }() if ok, _ := CSRF.Validate(ctx.User.ID, c.PostForm("csrf")); !ok { addMessage(c, errorMessage{T(c, "Your session has expired. Please try redoing what you were trying to do.")}) return } db.Exec("DELETE FROM osin_access WHERE client = ? AND extra = ?", c.PostForm("client_id"), ctx.User.ID) addMessage(c, successMessage{T(c, "That authorization has been successfully revoked.")}) }