mirror of https://github.com/cbeuw/Cloak
Implement admin control through a tunneled RESTful API
This commit is contained in:
parent
98a772b6ee
commit
2ce6f380d1
|
|
@ -4,25 +4,7 @@ package main
|
|||
|
||||
// TODO: rewrite this. Think of another way of admin control
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/cbeuw/Cloak/internal/client"
|
||||
"github.com/cbeuw/Cloak/internal/client/TLS"
|
||||
"github.com/cbeuw/Cloak/internal/util"
|
||||
)
|
||||
|
||||
/*
|
||||
type UserInfo struct {
|
||||
UID []byte
|
||||
// ALL of the following fields have to be accessed atomically
|
||||
|
|
@ -276,3 +258,4 @@ func (a *administrator) checkAndDecrypt(data []byte) ([]byte, error) {
|
|||
stream.XORKeyStream(ret, ret)
|
||||
return ret, nil
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -96,7 +97,7 @@ func main() {
|
|||
// The proxy port,should be 443
|
||||
var remotePort string
|
||||
var config string
|
||||
isAdmin := new(bool)
|
||||
var b64AdminUID string
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
|
|
@ -114,8 +115,8 @@ func main() {
|
|||
flag.StringVar(&remoteHost, "s", "", "remoteHost: IP of your proxy server")
|
||||
flag.StringVar(&remotePort, "p", "443", "remotePort: proxy port, should be 443")
|
||||
flag.StringVar(&config, "c", "ckclient.json", "config: path to the configuration file or options seperated with semicolons")
|
||||
flag.StringVar(&b64AdminUID, "a", "", "adminUID: enter the adminUID to serve the admin api")
|
||||
askVersion := flag.Bool("v", false, "Print the version number")
|
||||
isAdmin = flag.Bool("a", false, "Admin mode")
|
||||
printUsage := flag.Bool("h", false, "Print this message")
|
||||
flag.Parse()
|
||||
|
||||
|
|
@ -132,19 +133,6 @@ func main() {
|
|||
log.Println("Starting standalone mode")
|
||||
}
|
||||
|
||||
if *isAdmin {
|
||||
sta := client.InitState("", "", "", "", time.Now)
|
||||
err := sta.ParseConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = adminPrompt(sta)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
sta := client.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
|
||||
err := sta.ParseConfig(config)
|
||||
if err != nil {
|
||||
|
|
@ -164,28 +152,40 @@ func main() {
|
|||
if sta.TicketTimeHint == 0 {
|
||||
log.Fatal("TicketTimeHint cannot be empty or 0")
|
||||
}
|
||||
|
||||
listeningIP := sta.LocalHost
|
||||
if net.ParseIP(listeningIP).To4() == nil {
|
||||
// IPv6 needs square brackets
|
||||
listeningIP = "[" + listeningIP + "]"
|
||||
}
|
||||
listener, err := net.Listen("tcp", listeningIP+":"+sta.LocalPort)
|
||||
log.Println("Listening for proxy clients on " + listeningIP + ":" + sta.LocalPort)
|
||||
log.Println("Listening on " + listeningIP + ":" + sta.LocalPort)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var adminUID []byte
|
||||
if b64AdminUID != "" {
|
||||
adminUID, err = base64.StdEncoding.DecodeString(b64AdminUID)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
start:
|
||||
log.Println("Attemtping to start a new session")
|
||||
// sessionID is usergenerated. There shouldn't be a security concern because the scope of
|
||||
// sessionID is limited to its UID.
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
sessionID := rand.Uint32()
|
||||
sta.SetSessionID(sessionID)
|
||||
var UNLIMITED_DOWN int64 = 1e15
|
||||
var UNLIMITED_UP int64 = 1e15
|
||||
valve := mux.MakeValve(1e12, 1e12, &UNLIMITED_DOWN, &UNLIMITED_UP)
|
||||
|
||||
if adminUID != nil {
|
||||
sessionID = 0
|
||||
sta.UID = adminUID
|
||||
sta.NumConn = 1
|
||||
}
|
||||
|
||||
sta.SetSessionID(sessionID)
|
||||
var crypto mux.Crypto
|
||||
switch sta.EncryptionMethod {
|
||||
case 0x00:
|
||||
|
|
@ -207,7 +207,8 @@ start:
|
|||
sessionKey := make([]byte, 32)
|
||||
rand.Read(sessionKey)
|
||||
sta.SessionKey = sessionKey
|
||||
sesh := mux.MakeSession(sessionID, valve, mux.MakeObfs(sta.SessionKey, crypto), mux.MakeDeobfs(sta.SessionKey, crypto), util.ReadTLS)
|
||||
|
||||
sesh := mux.MakeSession(sessionID, mux.UNLIMITED_VALVE, mux.MakeObfs(sta.SessionKey, crypto), mux.MakeDeobfs(sta.SessionKey, crypto), util.ReadTLS)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < sta.NumConn; i++ {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -81,70 +83,6 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
|||
return
|
||||
}
|
||||
|
||||
finishHandshake := func() error {
|
||||
reply := server.ComposeReply(ch)
|
||||
_, err = conn.Write(reply)
|
||||
if err != nil {
|
||||
go conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// Two discarded messages: ChangeCipherSpec and Finished
|
||||
discardBuf := make([]byte, 1024)
|
||||
for c := 0; c < 2; c++ {
|
||||
_, err = util.ReadTLS(conn, discardBuf)
|
||||
if err != nil {
|
||||
go conn.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
// adminUID can use the server as normal with unlimited QoS credits. The adminUID is not
|
||||
// added to the userinfo database. The distinction between going into the admin mode
|
||||
// and normal proxy mode is that sessionID needs == 0 for admin mode
|
||||
if bytes.Equal(UID, sta.AdminUID) && sessionID == 0 {
|
||||
err = finishHandshake()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
c := sta.Userpanel.MakeController(sta.AdminUID)
|
||||
for {
|
||||
n, err := conn.Read(data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
resp, err := c.HandleRequest(data[:n])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
_, err = conn.Write(resp)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
user, err := sta.Panel.GetUser(UID)
|
||||
if err != nil {
|
||||
log.Printf("+1 unauthorised user from %v, uid: %x\n", conn.RemoteAddr(), UID)
|
||||
goWeb(data)
|
||||
return
|
||||
}
|
||||
|
||||
err = finishHandshake()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var crypto mux.Crypto
|
||||
switch encryptionMethod {
|
||||
case 0x00:
|
||||
|
|
@ -169,7 +107,62 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
|||
return
|
||||
}
|
||||
|
||||
sesh, existing, err := user.GetSession(sessionID, mux.MakeObfs(sessionKey, crypto), mux.MakeDeobfs(sessionKey, crypto), util.ReadTLS)
|
||||
obfs := mux.MakeObfs(sessionKey, crypto)
|
||||
deobfs := mux.MakeDeobfs(sessionKey, crypto)
|
||||
|
||||
finishHandshake := func() error {
|
||||
reply := server.ComposeReply(ch)
|
||||
_, err = conn.Write(reply)
|
||||
if err != nil {
|
||||
go conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// Two discarded messages: ChangeCipherSpec and Finished
|
||||
discardBuf := make([]byte, 1024)
|
||||
for c := 0; c < 2; c++ {
|
||||
_, err = util.ReadTLS(conn, discardBuf)
|
||||
if err != nil {
|
||||
go conn.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// adminUID can use the server as normal with unlimited QoS credits. The adminUID is not
|
||||
// added to the userinfo database. The distinction between going into the admin mode
|
||||
// and normal proxy mode is that sessionID needs == 0 for admin mode
|
||||
if bytes.Equal(UID, sta.AdminUID) && sessionID == 0 {
|
||||
err = finishHandshake()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
sesh := mux.MakeSession(0, mux.UNLIMITED_VALVE, obfs, deobfs, util.ReadTLS)
|
||||
sesh.AddConnection(conn)
|
||||
//TODO: Router could be nil in cnc mode
|
||||
err = http.Serve(sesh, sta.LocalAPIRouter)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user, err := sta.Panel.GetUser(UID)
|
||||
if err != nil {
|
||||
log.Printf("+1 unauthorised user from %v, uid: %v\n", conn.RemoteAddr(), base64.StdEncoding.EncodeToString(UID))
|
||||
goWeb(data)
|
||||
return
|
||||
}
|
||||
|
||||
err = finishHandshake()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
sesh, existing, err := user.GetSession(sessionID, obfs, deobfs, util.ReadTLS)
|
||||
if err != nil {
|
||||
user.DelSession(sessionID)
|
||||
log.Println(err)
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -2,6 +2,7 @@ module github.com/cbeuw/Cloak
|
|||
|
||||
require (
|
||||
github.com/boltdb/bolt v1.3.1
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/juju/ratelimit v1.0.1
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -1,5 +1,7 @@
|
|||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY=
|
||||
github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
|
|
|
|||
|
|
@ -21,16 +21,19 @@ type Valve struct {
|
|||
tx *int64
|
||||
}
|
||||
|
||||
func MakeValve(rxRate, txRate int64, rx, tx *int64) *Valve {
|
||||
func MakeValve(rxRate, txRate int64) *Valve {
|
||||
var rx, tx int64
|
||||
v := &Valve{
|
||||
rx: rx,
|
||||
tx: tx,
|
||||
rx: &rx,
|
||||
tx: &tx,
|
||||
}
|
||||
v.SetRxRate(rxRate)
|
||||
v.SetTxRate(txRate)
|
||||
return v
|
||||
}
|
||||
|
||||
var UNLIMITED_VALVE = MakeValve(1<<63-1, 1<<63-1)
|
||||
|
||||
func (v *Valve) SetRxRate(rate int64) { v.rxtb.Store(ratelimit.NewBucketWithRate(float64(rate), rate)) }
|
||||
func (v *Valve) SetTxRate(rate int64) { v.txtb.Store(ratelimit.NewBucketWithRate(float64(rate), rate)) }
|
||||
func (v *Valve) rxWait(n int) { v.rxtb.Load().(*ratelimit.Bucket).Wait(int64(n)) }
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ type Session struct {
|
|||
// Switchboard manages all connections to remote
|
||||
sb *switchboard
|
||||
|
||||
addrs atomic.Value
|
||||
|
||||
// For accepting new streams
|
||||
acceptCh chan *Stream
|
||||
|
||||
|
|
@ -56,6 +58,7 @@ func MakeSession(id uint32, valve *Valve, obfs Obfser, deobfs Deobfser, obfsedRe
|
|||
acceptCh: make(chan *Stream, acceptBacklog),
|
||||
die: make(chan struct{}),
|
||||
}
|
||||
sesh.addrs.Store([]net.Addr{nil, nil})
|
||||
sesh.sb = makeSwitchboard(sesh, valve)
|
||||
go sesh.timeoutAfter(30 * time.Second)
|
||||
return sesh
|
||||
|
|
@ -63,6 +66,8 @@ func MakeSession(id uint32, valve *Valve, obfs Obfser, deobfs Deobfser, obfsedRe
|
|||
|
||||
func (sesh *Session) AddConnection(conn net.Conn) {
|
||||
sesh.sb.addConn(conn)
|
||||
addrs := []net.Addr{conn.LocalAddr(), conn.RemoteAddr()}
|
||||
sesh.addrs.Store(addrs)
|
||||
}
|
||||
|
||||
func (sesh *Session) OpenStream() (*Stream, error) {
|
||||
|
|
@ -174,5 +179,4 @@ func (sesh *Session) timeoutAfter(to time.Duration) {
|
|||
}
|
||||
}
|
||||
|
||||
// Addr is only for implementing net.Listener
|
||||
func (sesh *Session) Addr() net.Addr { return nil }
|
||||
func (sesh *Session) Addr() net.Addr { return sesh.addrs.Load().([]net.Addr)[0] }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package multiplex
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
|
|
@ -29,6 +31,8 @@ type Stream struct {
|
|||
newFrameCh chan *Frame
|
||||
// sortedBufCh are order-sorted data ready to be read raw
|
||||
sortedBufCh chan []byte
|
||||
feederR *io.PipeReader
|
||||
feederW *io.PipeWriter
|
||||
|
||||
// atomic
|
||||
nextSendSeq uint32
|
||||
|
|
@ -41,6 +45,7 @@ type Stream struct {
|
|||
}
|
||||
|
||||
func makeStream(id uint32, sesh *Session) *Stream {
|
||||
r, w := io.Pipe()
|
||||
stream := &Stream{
|
||||
id: id,
|
||||
session: sesh,
|
||||
|
|
@ -48,11 +53,32 @@ func makeStream(id uint32, sesh *Session) *Stream {
|
|||
sh: []*frameNode{},
|
||||
newFrameCh: make(chan *Frame, 1024),
|
||||
sortedBufCh: make(chan []byte, 1024),
|
||||
feederR: r,
|
||||
feederW: w,
|
||||
}
|
||||
go stream.recvNewFrame()
|
||||
go stream.feed()
|
||||
return stream
|
||||
}
|
||||
|
||||
func (stream *Stream) feed() {
|
||||
for {
|
||||
select {
|
||||
case <-stream.die:
|
||||
return
|
||||
case data := <-stream.sortedBufCh:
|
||||
if len(data) == 0 {
|
||||
stream.passiveClose()
|
||||
return
|
||||
}
|
||||
_, err := stream.feederW.Write(data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (stream *Stream) Read(buf []byte) (n int, err error) {
|
||||
if len(buf) == 0 {
|
||||
select {
|
||||
|
|
@ -65,16 +91,8 @@ func (stream *Stream) Read(buf []byte) (n int, err error) {
|
|||
select {
|
||||
case <-stream.die:
|
||||
return 0, ErrBrokenStream
|
||||
case data := <-stream.sortedBufCh:
|
||||
if len(data) == 0 {
|
||||
stream.passiveClose()
|
||||
return 0, ErrBrokenStream
|
||||
}
|
||||
if len(buf) < len(data) {
|
||||
return 0, errors.New("buf too small")
|
||||
}
|
||||
copy(buf, data)
|
||||
return len(data), nil
|
||||
default:
|
||||
return stream.feederR.Read(buf)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -163,8 +181,8 @@ func (stream *Stream) closeNoDelMap() {
|
|||
// they are not used
|
||||
var errNotImplemented = errors.New("Not implemented")
|
||||
|
||||
func (stream *Stream) LocalAddr() net.Addr { return nil }
|
||||
func (stream *Stream) RemoteAddr() net.Addr { return nil }
|
||||
func (stream *Stream) LocalAddr() net.Addr { return stream.session.addrs.Load().([]net.Addr)[0] }
|
||||
func (stream *Stream) RemoteAddr() net.Addr { return stream.session.addrs.Load().([]net.Addr)[1] }
|
||||
|
||||
// TODO: implement the following
|
||||
func (stream *Stream) SetDeadline(t time.Time) error { return errNotImplemented }
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func (u *ActiveUser) GetSession(sessionID uint32, obfs mux.Obfser, deobfs mux.De
|
|||
u.sessionsM.Unlock()
|
||||
return sesh, true, nil
|
||||
} else {
|
||||
err := u.panel.manager.authoriseNewSession(u)
|
||||
err := u.panel.Manager.authoriseNewSession(u)
|
||||
if err != nil {
|
||||
u.sessionsM.Unlock()
|
||||
return nil, false, err
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"io/ioutil"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gmux "github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type rawConfig struct {
|
||||
|
|
@ -36,7 +38,8 @@ type State struct {
|
|||
usedRandomM sync.RWMutex
|
||||
usedRandom map[[32]byte]int
|
||||
|
||||
Panel *userPanel
|
||||
Panel *userPanel
|
||||
LocalAPIRouter *gmux.Router
|
||||
}
|
||||
|
||||
func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, error) {
|
||||
|
|
@ -76,6 +79,7 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
|||
return err
|
||||
}
|
||||
sta.Panel = MakeUserPanel(manager)
|
||||
sta.LocalAPIRouter = manager.Router
|
||||
}
|
||||
|
||||
sta.RedirAddr = preParse.RedirAddr
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
swagger: '2.0'
|
||||
info:
|
||||
description: |
|
||||
This is the API of Cloak server
|
||||
version: 1.0.0
|
||||
title: Cloak Server
|
||||
contact:
|
||||
email: cbeuw.andy@gmail.com
|
||||
license:
|
||||
name: GPLv3
|
||||
url: https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
# host: petstore.swagger.io
|
||||
# basePath: /v2
|
||||
tags:
|
||||
- name: admin
|
||||
description: Endpoints used by the host administrators
|
||||
- name: users
|
||||
description: Operations related to user controls by admin
|
||||
# schemes:
|
||||
# - http
|
||||
paths:
|
||||
/admin/users:
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
- users
|
||||
summary: Show all users
|
||||
description: Returns an array of all UserInfo
|
||||
operationId: listAllUsers
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/UserInfo'
|
||||
500:
|
||||
description: internal error
|
||||
/admin/users/{UID}:
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
- users
|
||||
summary: Show userinfo by UID
|
||||
description: Returns a UserInfo object
|
||||
operationId: getUserInfo
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- name: UID
|
||||
in: path
|
||||
description: UID of the user
|
||||
required: true
|
||||
type: string
|
||||
format: byte
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
schema:
|
||||
$ref: '#/definitions/UserInfo'
|
||||
400:
|
||||
description: bad request
|
||||
404:
|
||||
description: User not found
|
||||
500:
|
||||
description: internal error
|
||||
post:
|
||||
tags:
|
||||
- admin
|
||||
- users
|
||||
summary: Updates the userinfo of the specified user, if the user does not exist, then a new user is created
|
||||
operationId: writeUserInfo
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- name: UID
|
||||
in: path
|
||||
description: UID of the user
|
||||
required: true
|
||||
type: string
|
||||
format: byte
|
||||
- name: UserInfo
|
||||
in: body
|
||||
description: New userinfo
|
||||
required: true
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/UserInfo'
|
||||
responses:
|
||||
201:
|
||||
description: successful operation
|
||||
400:
|
||||
description: bad request
|
||||
500:
|
||||
description: internal error
|
||||
delete:
|
||||
tags:
|
||||
- admin
|
||||
- users
|
||||
summary: Deletes a user
|
||||
operationId: deleteUser
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- name: UID
|
||||
in: path
|
||||
description: UID of the user to be deleted
|
||||
required: true
|
||||
type: string
|
||||
format: byte
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
400:
|
||||
description: bad request
|
||||
404:
|
||||
description: User not found
|
||||
500:
|
||||
description: internal error
|
||||
|
||||
definitions:
|
||||
UserInfo:
|
||||
type: object
|
||||
properties:
|
||||
UID:
|
||||
type: string
|
||||
format: byte
|
||||
SessionsCap:
|
||||
type: integer
|
||||
format: int32
|
||||
UpRate:
|
||||
type: integer
|
||||
format: int64
|
||||
DownRate:
|
||||
type: integer
|
||||
format: int64
|
||||
UpCredit:
|
||||
type: integer
|
||||
format: int64
|
||||
DownCredit:
|
||||
type: integer
|
||||
format: int64
|
||||
ExpiryTime:
|
||||
type: integer
|
||||
format: int64
|
||||
externalDocs:
|
||||
description: Find out more about Swagger
|
||||
url: http://swagger.io
|
||||
# Added by API Auto Mocking Plugin
|
||||
host: virtserver.swaggerhub.com
|
||||
basePath: /cbeuw/ck-server/1.0.0
|
||||
schemes:
|
||||
- https
|
||||
- http
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
gmux "github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var Uint32 = binary.BigEndian.Uint32
|
||||
|
|
@ -13,8 +14,20 @@ var Uint64 = binary.BigEndian.Uint64
|
|||
var PutUint32 = binary.BigEndian.PutUint32
|
||||
var PutUint64 = binary.BigEndian.PutUint64
|
||||
|
||||
func i64ToB(value int64) []byte {
|
||||
oct := make([]byte, 8)
|
||||
PutUint64(oct, uint64(value))
|
||||
return oct
|
||||
}
|
||||
func i32ToB(value int32) []byte {
|
||||
nib := make([]byte, 4)
|
||||
PutUint32(nib, uint32(value))
|
||||
return nib
|
||||
}
|
||||
|
||||
type localManager struct {
|
||||
db *bolt.DB
|
||||
db *bolt.DB
|
||||
Router *gmux.Router
|
||||
}
|
||||
|
||||
func MakeLocalManager(dbPath string) (*localManager, error) {
|
||||
|
|
@ -22,7 +35,20 @@ func MakeLocalManager(dbPath string) (*localManager, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &localManager{db}, nil
|
||||
ret := &localManager{
|
||||
db: db,
|
||||
}
|
||||
ret.Router = ret.registerMux()
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (manager *localManager) registerMux() *gmux.Router {
|
||||
r := gmux.NewRouter()
|
||||
r.HandleFunc("/admin/users", manager.listAllUsersHlr).Methods("GET")
|
||||
r.HandleFunc("/admin/users/{UID}", manager.getUserInfo).Methods("GET")
|
||||
r.HandleFunc("/admin/users/{UID}", manager.writeUserInfo).Methods("POST")
|
||||
r.HandleFunc("/admin/users/{UID}", manager.deleteUser).Methods("DELETE")
|
||||
return r
|
||||
}
|
||||
|
||||
func (manager *localManager) authenticateUser(UID []byte) (int64, int64, error) {
|
||||
|
|
@ -90,12 +116,6 @@ func (manager *localManager) authoriseNewSession(user *ActiveUser) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func i64ToB(value int64) []byte {
|
||||
oct := make([]byte, 8)
|
||||
PutUint64(oct, uint64(value))
|
||||
return oct
|
||||
}
|
||||
|
||||
func (manager *localManager) uploadStatus(uploads []statusUpdate) ([]statusResponse, error) {
|
||||
var responses []statusResponse
|
||||
err := manager.db.Update(func(tx *bolt.Tx) error {
|
||||
|
|
@ -103,7 +123,12 @@ func (manager *localManager) uploadStatus(uploads []statusUpdate) ([]statusRespo
|
|||
var resp statusResponse
|
||||
bucket := tx.Bucket(status.UID)
|
||||
if bucket == nil {
|
||||
log.Printf("%x doesn't exist\n", status.UID)
|
||||
resp = statusResponse{
|
||||
status.UID,
|
||||
TERMINATE,
|
||||
"User no longer exists",
|
||||
}
|
||||
responses = append(responses, resp)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -115,8 +140,14 @@ func (manager *localManager) uploadStatus(uploads []statusUpdate) ([]statusRespo
|
|||
TERMINATE,
|
||||
"No upload credit left",
|
||||
}
|
||||
responses = append(responses, resp)
|
||||
continue
|
||||
}
|
||||
err := bucket.Put([]byte("UpCredit"), i64ToB(newUp))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
bucket.Put([]byte("UpCredit"), i64ToB(newUp))
|
||||
|
||||
oldDown := int64(Uint64(bucket.Get([]byte("DownCredit"))))
|
||||
newDown := oldDown - status.downUsage
|
||||
|
|
@ -126,20 +157,24 @@ func (manager *localManager) uploadStatus(uploads []statusUpdate) ([]statusRespo
|
|||
TERMINATE,
|
||||
"No download credit left",
|
||||
}
|
||||
responses = append(responses, resp)
|
||||
continue
|
||||
}
|
||||
err = bucket.Put([]byte("DownCredit"), i64ToB(newDown))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
bucket.Put([]byte("DownCredit"), i64ToB(newDown))
|
||||
|
||||
expiry := int64(Uint64(bucket.Get([]byte("ExpiryTime"))))
|
||||
if time.Now().Unix()>expiry{
|
||||
if time.Now().Unix() > expiry {
|
||||
resp = statusResponse{
|
||||
status.UID,
|
||||
TERMINATE,
|
||||
"User has expired",
|
||||
}
|
||||
}
|
||||
|
||||
if resp.UID != nil {
|
||||
responses = append(responses, resp)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"github.com/boltdb/bolt"
|
||||
"net/http"
|
||||
|
||||
gmux "github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type UserInfo struct {
|
||||
UID []byte
|
||||
SessionsCap int
|
||||
UpRate int64
|
||||
DownRate int64
|
||||
UpCredit int64
|
||||
DownCredit int64
|
||||
ExpiryTime int64
|
||||
}
|
||||
|
||||
func (manager *localManager) listAllUsersHlr(w http.ResponseWriter, r *http.Request) {
|
||||
var infos []UserInfo
|
||||
_ = manager.db.View(func(tx *bolt.Tx) error {
|
||||
err := tx.ForEach(func(UID []byte, bucket *bolt.Bucket) error {
|
||||
var uinfo UserInfo
|
||||
uinfo.UID = UID
|
||||
uinfo.SessionsCap = int(Uint32(bucket.Get([]byte("SessionsCap"))))
|
||||
uinfo.UpRate = int64(Uint64(bucket.Get([]byte("UpRate"))))
|
||||
uinfo.DownRate = int64(Uint64(bucket.Get([]byte("DownRate"))))
|
||||
uinfo.UpCredit = int64(Uint64(bucket.Get([]byte("UpCredit"))))
|
||||
uinfo.DownCredit = int64(Uint64(bucket.Get([]byte("DownCredit"))))
|
||||
uinfo.ExpiryTime = int64(Uint64(bucket.Get([]byte("ExpiryTime"))))
|
||||
infos = append(infos, uinfo)
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
resp, err := json.Marshal(infos)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, _ = w.Write(resp)
|
||||
}
|
||||
|
||||
func (manager *localManager) getUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
b64UID := gmux.Vars(r)["UID"]
|
||||
if b64UID == "" {
|
||||
http.Error(w, "UID cannot be empty", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
UID, err := base64.URLEncoding.DecodeString(b64UID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var uinfo UserInfo
|
||||
err = manager.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(UID))
|
||||
if bucket == nil {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
uinfo.UID = UID
|
||||
uinfo.SessionsCap = int(Uint32(bucket.Get([]byte("SessionsCap"))))
|
||||
uinfo.UpRate = int64(Uint64(bucket.Get([]byte("UpRate"))))
|
||||
uinfo.DownRate = int64(Uint64(bucket.Get([]byte("DownRate"))))
|
||||
uinfo.UpCredit = int64(Uint64(bucket.Get([]byte("UpCredit"))))
|
||||
uinfo.DownCredit = int64(Uint64(bucket.Get([]byte("DownCredit"))))
|
||||
uinfo.ExpiryTime = int64(Uint64(bucket.Get([]byte("ExpiryTime"))))
|
||||
return nil
|
||||
})
|
||||
if err == ErrUserNotFound {
|
||||
http.Error(w, ErrUserNotFound.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
resp, err := json.Marshal(uinfo)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, _ = w.Write(resp)
|
||||
}
|
||||
|
||||
func (manager *localManager) writeUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
b64UID := gmux.Vars(r)["UID"]
|
||||
if b64UID == "" {
|
||||
http.Error(w, "UID cannot be empty", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
UID, err := base64.URLEncoding.DecodeString(b64UID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
jsonUinfo := gmux.Vars(r)["UserInfo"]
|
||||
if jsonUinfo == "" {
|
||||
http.Error(w, "UserInfo cannot be empty", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var uinfo UserInfo
|
||||
err = json.Unmarshal([]byte(jsonUinfo), &uinfo)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(UID, uinfo.UID) {
|
||||
http.Error(w, "UID mismatch", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
err = manager.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := tx.CreateBucket(uinfo.UID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = bucket.Put([]byte("SessionsCap"), i32ToB(int32(uinfo.SessionsCap))); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = bucket.Put([]byte("UpRate"), i64ToB(uinfo.UpRate)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = bucket.Put([]byte("DownRate"), i64ToB(uinfo.DownRate)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = bucket.Put([]byte("UpCredit"), i64ToB(uinfo.UpCredit)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = bucket.Put([]byte("DownCredit"), i64ToB(uinfo.DownCredit)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = bucket.Put([]byte("ExpiryTime"), i64ToB(uinfo.ExpiryTime)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
func (manager *localManager) deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
b64UID := gmux.Vars(r)["UID"]
|
||||
if b64UID == "" {
|
||||
http.Error(w, "UID cannot be empty", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
UID, err := base64.URLEncoding.DecodeString(b64UID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = manager.db.Update(func(tx *bolt.Tx) error {
|
||||
return tx.DeleteBucket(UID)
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
type userPanel struct {
|
||||
manager UserManager
|
||||
Manager UserManager
|
||||
|
||||
activeUsersM sync.RWMutex
|
||||
activeUsers map[[16]byte]*ActiveUser
|
||||
|
|
@ -20,7 +20,7 @@ type userPanel struct {
|
|||
|
||||
func MakeUserPanel(manager UserManager) *userPanel {
|
||||
ret := &userPanel{
|
||||
manager: manager,
|
||||
Manager: manager,
|
||||
activeUsers: make(map[[16]byte]*ActiveUser),
|
||||
usageUpdateQueue: make(map[[16]byte]*usagePair),
|
||||
}
|
||||
|
|
@ -37,13 +37,12 @@ func (panel *userPanel) GetUser(UID []byte) (*ActiveUser, error) {
|
|||
return user, nil
|
||||
}
|
||||
|
||||
upRate, downRate, err := panel.manager.authenticateUser(UID)
|
||||
upRate, downRate, err := panel.Manager.authenticateUser(UID)
|
||||
if err != nil {
|
||||
panel.activeUsersM.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
var upUsage, downUsage int64
|
||||
valve := mux.MakeValve(upRate, downRate, &upUsage, &downUsage)
|
||||
valve := mux.MakeValve(upRate, downRate)
|
||||
user := &ActiveUser{
|
||||
panel: panel,
|
||||
valve: valve,
|
||||
|
|
@ -124,7 +123,7 @@ func (panel *userPanel) commitUpdate() {
|
|||
statuses = append(statuses, status)
|
||||
}
|
||||
|
||||
responses, err := panel.manager.uploadStatus(statuses)
|
||||
responses, err := panel.Manager.uploadStatus(statuses)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue