446 lines
10 KiB
Go
446 lines
10 KiB
Go
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: <code>%s</code>.<br>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:<br>
|
|
<pre>client_id = "%s"
|
|
client_secret = "%s"</pre>
|
|
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.")})
|
|
}
|