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.")}) }