파일 업로드 추가 / 화이트리스트에 권한 종류 추가
This commit is contained in:
183
core/api.go
183
core/api.go
@ -1,13 +1,19 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -21,6 +27,54 @@ import (
|
|||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type fileDocumentDesc struct {
|
||||||
|
Service string
|
||||||
|
Key string
|
||||||
|
Src string
|
||||||
|
Link string
|
||||||
|
Desc string
|
||||||
|
Extract bool
|
||||||
|
Timestamp int64
|
||||||
|
Contents []byte `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd *fileDocumentDesc) save() error {
|
||||||
|
// 새 파일 올라옴
|
||||||
|
if len(fd.Contents) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var destFile string
|
||||||
|
if fd.Extract {
|
||||||
|
os.MkdirAll(fd.Link, os.ModePerm)
|
||||||
|
destFile = path.Join(fd.Link, fd.Src)
|
||||||
|
} else {
|
||||||
|
os.MkdirAll(path.Dir(fd.Link), os.ModePerm)
|
||||||
|
destFile = fd.Link
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(destFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(f, bytes.NewBuffer(fd.Contents))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fd.Extract {
|
||||||
|
switch path.Ext(destFile) {
|
||||||
|
case ".zip":
|
||||||
|
err = common.Unzip(destFile)
|
||||||
|
case ".tar":
|
||||||
|
err = common.Untar(destFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (caller apiCaller) isGlobalAdmin() bool {
|
func (caller apiCaller) isGlobalAdmin() bool {
|
||||||
if *noauth {
|
if *noauth {
|
||||||
return true
|
return true
|
||||||
@ -60,13 +114,18 @@ func (caller apiCaller) writeAccessableServices(w http.ResponseWriter) {
|
|||||||
|
|
||||||
func (caller apiCaller) getAccessableServices() ([]*serviceDescription, []string) {
|
func (caller apiCaller) getAccessableServices() ([]*serviceDescription, []string) {
|
||||||
allservices := caller.mg.services.all()
|
allservices := caller.mg.services.all()
|
||||||
v, ok := caller.userinfo["email"]
|
|
||||||
if !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
email := v.(string)
|
admin := caller.isGlobalAdmin()
|
||||||
_, admin := caller.admins[email]
|
var email string
|
||||||
|
if !*noauth {
|
||||||
|
v, ok := caller.userinfo["email"]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
email := v.(string)
|
||||||
|
_, admin = caller.admins[email]
|
||||||
|
}
|
||||||
|
|
||||||
var output []*serviceDescription
|
var output []*serviceDescription
|
||||||
var editable []string
|
var editable []string
|
||||||
@ -114,30 +173,79 @@ func (caller apiCaller) isValidUser(service any, category string) (valid bool, a
|
|||||||
return svcdesc.isValidAPIUser(category, email), false
|
return svcdesc.isValidAPIUser(category, email), false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (caller apiCaller) filesAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
serviceid := r.FormValue("service")
|
||||||
|
if len(serviceid) == 0 {
|
||||||
|
serviceid = "000000000000"
|
||||||
|
}
|
||||||
|
|
||||||
|
var files []fileDocumentDesc
|
||||||
|
err := caller.mg.mongoClient.FindAllAs(CollectionFile, bson.M{
|
||||||
|
"service": serviceid,
|
||||||
|
}, &files, options.Find().SetProjection(bson.M{
|
||||||
|
"contents": 0,
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
return enc.Encode(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
var seq = uint32(0)
|
||||||
|
|
||||||
func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error {
|
func (caller apiCaller) uploadAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
// file, header, err := r.FormFile("file")
|
serviceid := r.FormValue("service")
|
||||||
// if err != nil {
|
if len(serviceid) == 0 {
|
||||||
// logger.Error(err)
|
serviceid = "000000000000"
|
||||||
// w.WriteHeader(http.StatusBadRequest)
|
}
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// defer file.Close()
|
|
||||||
|
|
||||||
// contents, err := io.ReadAll(file)
|
infile, header, err := r.FormFile("file")
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// logger.Error(err)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
// w.WriteHeader(http.StatusInternalServerError)
|
return err
|
||||||
// return
|
}
|
||||||
// }
|
defer infile.Close()
|
||||||
|
|
||||||
// ext := path.Ext(header.Filename)
|
desc := r.FormValue("desc")
|
||||||
// if ext == ".zip" {
|
contents, _ := io.ReadAll(infile)
|
||||||
|
extractstr := r.FormValue("extract")
|
||||||
|
extract, _ := strconv.ParseBool(extractstr)
|
||||||
|
|
||||||
// }
|
var b [5]byte
|
||||||
|
binary.BigEndian.PutUint32(b[0:4], uint32(time.Now().Unix()))
|
||||||
|
b[4] = byte(atomic.AddUint32(&seq, 1) % 255)
|
||||||
|
rf := hex.EncodeToString(b[:])
|
||||||
|
|
||||||
// // deploys 폴더는 파일시스템 서비스이므로 다운로드 가능
|
var link string
|
||||||
// filename := path.Join("deploys", name, version, name+ext)
|
if extract {
|
||||||
return nil
|
link = path.Join("static", serviceid, rf)
|
||||||
|
} else {
|
||||||
|
link = path.Join("static", serviceid, rf, header.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
newdoc := fileDocumentDesc{
|
||||||
|
Contents: contents,
|
||||||
|
Src: header.Filename,
|
||||||
|
Timestamp: time.Now().UTC().Unix(),
|
||||||
|
Extract: extract,
|
||||||
|
Link: link,
|
||||||
|
Desc: desc,
|
||||||
|
Key: rf,
|
||||||
|
Service: serviceid,
|
||||||
|
}
|
||||||
|
_, _, err = caller.mg.mongoClient.UpsertOne(CollectionFile, bson.M{
|
||||||
|
"service": serviceid,
|
||||||
|
"key": rf,
|
||||||
|
}, newdoc)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
newdoc.Contents = nil
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
enc.Encode(newdoc)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) error {
|
func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
@ -215,25 +323,6 @@ func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (caller apiCaller) divisionAPI(w http.ResponseWriter, r *http.Request, svcid string, divid string) error {
|
|
||||||
// if r.Method == "PUT" {
|
|
||||||
// // svcid, divid에 statemeta 설정
|
|
||||||
// file, header, err := r.FormFile("file")
|
|
||||||
// if err != nil {
|
|
||||||
// logger.Error(err)
|
|
||||||
// w.WriteHeader(http.StatusBadRequest)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// defer file.Close()
|
|
||||||
|
|
||||||
// if header.
|
|
||||||
// stateFile, header, err := r.FormFile("file")
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error {
|
func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
mg := caller.mg
|
mg := caller.mg
|
||||||
queryvals := r.URL.Query()
|
queryvals := r.URL.Query()
|
||||||
@ -277,6 +366,10 @@ func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
filter := bson.M{"_id": service.Id}
|
filter := bson.M{"_id": service.Id}
|
||||||
|
if len(service.ServiceCode) == 0 {
|
||||||
|
service.ServiceCode = hex.EncodeToString(service.Id[6:])
|
||||||
|
}
|
||||||
|
|
||||||
success, _, err := mg.mongoClient.Update(CollectionService, filter, bson.M{
|
success, _, err := mg.mongoClient.Update(CollectionService, filter, bson.M{
|
||||||
"$set": &service,
|
"$set": &service,
|
||||||
}, options.Update().SetUpsert(true))
|
}, options.Update().SetUpsert(true))
|
||||||
@ -511,6 +604,8 @@ func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) {
|
|||||||
err = caller.accountAPI(w, r)
|
err = caller.accountAPI(w, r)
|
||||||
} else if strings.HasSuffix(r.URL.Path, "/upload") {
|
} else if strings.HasSuffix(r.URL.Path, "/upload") {
|
||||||
err = caller.uploadAPI(w, r)
|
err = caller.uploadAPI(w, r)
|
||||||
|
} else if strings.HasSuffix(r.URL.Path, "/files") {
|
||||||
|
err = caller.filesAPI(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -39,6 +39,7 @@ var (
|
|||||||
CollectionAuth = common.CollectionName("auth")
|
CollectionAuth = common.CollectionName("auth")
|
||||||
CollectionWhitelist = common.CollectionName("whitelist")
|
CollectionWhitelist = common.CollectionName("whitelist")
|
||||||
CollectionService = common.CollectionName("service")
|
CollectionService = common.CollectionName("service")
|
||||||
|
CollectionFile = common.CollectionName("file")
|
||||||
CollectionBlock = common.CollectionName("block")
|
CollectionBlock = common.CollectionName("block")
|
||||||
CollectionPlatformLoginToken = common.CollectionName("platform_login_token") //-- 각 플랫폼에 로그인 및 권한 받아오는 과정에 사용하는 Key
|
CollectionPlatformLoginToken = common.CollectionName("platform_login_token") //-- 각 플랫폼에 로그인 및 권한 받아오는 과정에 사용하는 Key
|
||||||
CollectionUserToken = common.CollectionName("usertoken")
|
CollectionUserToken = common.CollectionName("usertoken")
|
||||||
@ -434,6 +435,18 @@ func (mg *Maingate) prepare(context context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeIndices(CollectionFile, map[string]bson.D{
|
||||||
|
"service": {{Key: "service", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeUniqueIndices(CollectionFile, map[string]bson.D{
|
||||||
|
"sk": {{Key: "service", Value: 1}, {Key: "key", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err = mg.mongoClient.MakeExpireIndex(CollectionWhitelist, 10); err != nil {
|
if err = mg.mongoClient.MakeExpireIndex(CollectionWhitelist, 10); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -482,6 +495,36 @@ func (mg *Maingate) prepare(context context.Context) (err error) {
|
|||||||
|
|
||||||
mg.auths = makeAuthCollection(mg.mongoClient, time.Duration(mg.SessionTTL*int64(time.Second)))
|
mg.auths = makeAuthCollection(mg.mongoClient, time.Duration(mg.SessionTTL*int64(time.Second)))
|
||||||
|
|
||||||
|
var preall []struct {
|
||||||
|
Link string `bson:"link"`
|
||||||
|
Id primitive.ObjectID `bson:"_id"`
|
||||||
|
}
|
||||||
|
if err = mg.mongoClient.FindAllAs(CollectionFile, nil, &preall, options.Find().SetProjection(bson.M{
|
||||||
|
"link": 1,
|
||||||
|
})); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pre := range preall {
|
||||||
|
_, err := os.Stat(pre.Link)
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logger.Println("saving files :", pre.Link)
|
||||||
|
|
||||||
|
var fulldoc fileDocumentDesc
|
||||||
|
err = mg.mongoClient.FindOneAs(CollectionFile, bson.M{
|
||||||
|
"_id": pre.Id,
|
||||||
|
}, &fulldoc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = fulldoc.save()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go watchAuthCollection(context, mg.auths, mg.mongoClient)
|
go watchAuthCollection(context, mg.auths, mg.mongoClient)
|
||||||
go mg.watchWhitelistCollection(context)
|
go mg.watchWhitelistCollection(context)
|
||||||
|
|
||||||
@ -579,7 +622,7 @@ func (mg *Maingate) RegisterHandlers(ctx context.Context, serveMux *http.ServeMu
|
|||||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_authorize_sdk)
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_authorize_sdk)
|
||||||
|
|
||||||
go mg.watchServiceCollection(ctx, serveMux, prefix)
|
go mg.watchServiceCollection(ctx, serveMux, prefix)
|
||||||
|
go mg.watchFileCollection(ctx, serveMux, prefix)
|
||||||
// fsx := http.FileServer(http.Dir("console"))
|
// fsx := http.FileServer(http.Dir("console"))
|
||||||
// serveMux.Handle("/console/", http.StripPrefix("/console/", fsx))
|
// serveMux.Handle("/console/", http.StripPrefix("/console/", fsx))
|
||||||
// logger.Println("console file server open")
|
// logger.Println("console file server open")
|
||||||
|
|||||||
213
core/service.go
213
core/service.go
@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -26,16 +27,25 @@ type blockinfo struct {
|
|||||||
Reason string
|
Reason string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type whitelistAuthType = string
|
||||||
|
|
||||||
|
const (
|
||||||
|
whitelistAuthType_Default = whitelistAuthType("")
|
||||||
|
whitelistAuthType_QA = whitelistAuthType("qa")
|
||||||
|
)
|
||||||
|
|
||||||
type whitelistmember struct {
|
type whitelistmember struct {
|
||||||
Service string
|
Service string
|
||||||
Email string
|
Email string
|
||||||
Platform string
|
Platform string
|
||||||
Desc string
|
Desc string
|
||||||
|
Auth []whitelistAuthType
|
||||||
Expired primitive.DateTime `bson:"_ts,omitempty" json:"_ts,omitempty"`
|
Expired primitive.DateTime `bson:"_ts,omitempty" json:"_ts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type whitelist struct {
|
type whitelist struct {
|
||||||
emailptr unsafe.Pointer
|
emailptr unsafe.Pointer
|
||||||
|
qaptr unsafe.Pointer
|
||||||
working int32
|
working int32
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,17 +60,33 @@ type usertokeninfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (wl *whitelist) init(total []whitelistmember) {
|
func (wl *whitelist) init(total []whitelistmember) {
|
||||||
next := make(map[string]*whitelistmember)
|
auths := make(map[string]map[string]*whitelistmember)
|
||||||
for _, member := range total {
|
for _, member := range total {
|
||||||
next[whitelistKey(member.Email)] = &member
|
all := auths[""]
|
||||||
}
|
if all == nil {
|
||||||
|
all = make(map[string]*whitelistmember)
|
||||||
|
auths[""] = all
|
||||||
|
}
|
||||||
|
all[whitelistKey(member.Email)] = &member
|
||||||
|
|
||||||
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&next))
|
for _, auth := range member.Auth {
|
||||||
atomic.StoreInt32(&wl.working, 1)
|
spec := auths[auth]
|
||||||
|
if spec == nil {
|
||||||
|
spec = make(map[string]*whitelistmember)
|
||||||
|
auths[auth] = spec
|
||||||
|
}
|
||||||
|
spec[whitelistKey(member.Email)] = &member
|
||||||
|
}
|
||||||
|
}
|
||||||
|
all := auths[whitelistAuthType_Default]
|
||||||
|
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&all))
|
||||||
|
|
||||||
|
qa := auths[whitelistAuthType_QA]
|
||||||
|
atomic.StorePointer(&wl.qaptr, unsafe.Pointer(&qa))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wl *whitelist) add(m *whitelistmember) {
|
func addToUnsafePointer(to *unsafe.Pointer, m *whitelistmember) {
|
||||||
ptr := atomic.LoadPointer(&wl.emailptr)
|
ptr := atomic.LoadPointer(to)
|
||||||
src := (*map[string]*whitelistmember)(ptr)
|
src := (*map[string]*whitelistmember)(ptr)
|
||||||
|
|
||||||
next := map[string]*whitelistmember{}
|
next := map[string]*whitelistmember{}
|
||||||
@ -68,11 +94,11 @@ func (wl *whitelist) add(m *whitelistmember) {
|
|||||||
next[k] = v
|
next[k] = v
|
||||||
}
|
}
|
||||||
next[whitelistKey(m.Email)] = m
|
next[whitelistKey(m.Email)] = m
|
||||||
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&next))
|
atomic.StorePointer(to, unsafe.Pointer(&next))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wl *whitelist) remove(email string) {
|
func removeFromUnsafePointer(from *unsafe.Pointer, email string) {
|
||||||
ptr := atomic.LoadPointer(&wl.emailptr)
|
ptr := atomic.LoadPointer(from)
|
||||||
src := (*map[string]*whitelistmember)(ptr)
|
src := (*map[string]*whitelistmember)(ptr)
|
||||||
|
|
||||||
next := make(map[string]*whitelistmember)
|
next := make(map[string]*whitelistmember)
|
||||||
@ -80,7 +106,21 @@ func (wl *whitelist) remove(email string) {
|
|||||||
next[k] = v
|
next[k] = v
|
||||||
}
|
}
|
||||||
delete(next, whitelistKey(email))
|
delete(next, whitelistKey(email))
|
||||||
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&next))
|
atomic.StorePointer(from, unsafe.Pointer(&next))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wl *whitelist) add(m *whitelistmember) {
|
||||||
|
addToUnsafePointer(&wl.emailptr, m)
|
||||||
|
for _, auth := range m.Auth {
|
||||||
|
if auth == whitelistAuthType_QA {
|
||||||
|
addToUnsafePointer(&wl.qaptr, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wl *whitelist) remove(email string) {
|
||||||
|
removeFromUnsafePointer(&wl.emailptr, email)
|
||||||
|
removeFromUnsafePointer(&wl.qaptr, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wl *whitelist) isMember(email string, platform string) bool {
|
func (wl *whitelist) isMember(email string, platform string) bool {
|
||||||
@ -97,30 +137,46 @@ func (wl *whitelist) isMember(email string, platform string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wl *whitelist) hasAuth(email string, platform string, auth whitelistAuthType) bool {
|
||||||
|
if auth == whitelistAuthType_QA {
|
||||||
|
ptr := atomic.LoadPointer(&wl.qaptr)
|
||||||
|
src := *(*map[string]*whitelistmember)(ptr)
|
||||||
|
|
||||||
|
if member, exists := src[whitelistKey(email)]; exists {
|
||||||
|
return member.Platform == platform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type divisionStateName string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DivisionState_Closed = string("closed")
|
DivisionState_Closed = divisionStateName("closed")
|
||||||
DivisionState_Maintenance = string("maintenance")
|
DivisionState_Maintenance = divisionStateName("maintenance")
|
||||||
DivisionState_RestrictedOpen = string("restricted")
|
DivisionState_RestrictedOpen = divisionStateName("restricted")
|
||||||
DivisionState_FullOpen = string("open")
|
DivisionState_FullOpen = divisionStateName("open")
|
||||||
)
|
)
|
||||||
|
|
||||||
type maintenance struct {
|
type maintenance struct {
|
||||||
Link string
|
Notice string `bson:"notice"`
|
||||||
StartTime primitive.Timestamp
|
StartTimeUTC int64 `bson:"start_unixtime_utc" json:"start_unixtime_utc"`
|
||||||
|
link string
|
||||||
}
|
}
|
||||||
|
|
||||||
type division struct {
|
type division struct {
|
||||||
Url string `bson:"url"`
|
Url string `bson:"url"`
|
||||||
Priority int `bson:"priority"`
|
Priority int `bson:"priority"`
|
||||||
State string `bson:"state"`
|
State divisionStateName `bson:"state"`
|
||||||
Maintenance maintenance `bson:"maintenance"`
|
Maintenance *maintenance `bson:"maintenance"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type serviceDescription struct {
|
type serviceDescription struct {
|
||||||
// sync.Mutex
|
// sync.Mutex
|
||||||
Id primitive.ObjectID `bson:"_id"`
|
Id primitive.ObjectID `bson:"_id"`
|
||||||
ServiceName string `bson:"service"`
|
ServiceName string `bson:"service"`
|
||||||
Divisions map[string]division `bson:"divisions"`
|
Divisions map[string]*division `bson:"divisions"`
|
||||||
ServiceCode string `bson:"code"`
|
ServiceCode string `bson:"code"`
|
||||||
UseWhitelist bool `bson:"use_whitelist"`
|
UseWhitelist bool `bson:"use_whitelist"`
|
||||||
Closed bool `bson:"closed"`
|
Closed bool `bson:"closed"`
|
||||||
@ -175,12 +231,46 @@ func (sh *serviceDescription) readProfile(authtype string, id string, binfo stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sh *serviceDescription) prepare(mg *Maingate) error {
|
func (sh *serviceDescription) prepare(mg *Maingate) error {
|
||||||
div := sh.Divisions
|
divs := sh.Divisions
|
||||||
if len(sh.ServiceCode) == 0 {
|
if len(sh.ServiceCode) == 0 {
|
||||||
sh.ServiceCode = hex.EncodeToString(sh.Id[6:])
|
sh.ServiceCode = hex.EncodeToString(sh.Id[6:])
|
||||||
}
|
}
|
||||||
|
|
||||||
divmarshaled, _ := json.Marshal(div)
|
var closed []string
|
||||||
|
for dn, div := range divs {
|
||||||
|
if div.State == DivisionState_Closed {
|
||||||
|
closed = append(closed, dn)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(div.State) == 0 {
|
||||||
|
div.State = DivisionState_FullOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if div.State != DivisionState_FullOpen {
|
||||||
|
if div.Maintenance == nil {
|
||||||
|
div.Maintenance = &maintenance{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(div.Maintenance.link) == 0 {
|
||||||
|
if len(div.Maintenance.Notice) == 0 {
|
||||||
|
div.Maintenance.link = "https://www.action2quare.com"
|
||||||
|
} else if strings.HasPrefix(div.Maintenance.Notice, "http") {
|
||||||
|
div.Maintenance.link = div.Maintenance.Notice
|
||||||
|
} else {
|
||||||
|
div.Maintenance.link = path.Join("static", sh.ServiceCode, div.Maintenance.Notice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
div.Maintenance = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dn := range closed {
|
||||||
|
delete(divs, dn)
|
||||||
|
}
|
||||||
|
|
||||||
|
divmarshaled, _ := json.Marshal(divs)
|
||||||
devstr := string(divmarshaled)
|
devstr := string(divmarshaled)
|
||||||
sh.divisionsSerialized = unsafe.Pointer(&devstr)
|
sh.divisionsSerialized = unsafe.Pointer(&devstr)
|
||||||
|
|
||||||
@ -200,15 +290,17 @@ func (sh *serviceDescription) prepare(mg *Maingate) error {
|
|||||||
sh.closed = 0
|
sh.closed = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if sh.UseWhitelist {
|
var whites []whitelistmember
|
||||||
var whites []whitelistmember
|
if err := mg.mongoClient.FindAllAs(CollectionWhitelist, bson.M{
|
||||||
if err := mg.mongoClient.FindAllAs(CollectionWhitelist, bson.M{
|
"$or": []bson.M{{"service": sh.ServiceName}, {"service": sh.ServiceCode}},
|
||||||
"$or": []bson.M{{"service": sh.ServiceName}, {"service": sh.ServiceCode}},
|
}, &whites, options.Find().SetReturnKey(false)); err != nil {
|
||||||
}, &whites, options.Find().SetReturnKey(false)); err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sh.wl.init(whites)
|
sh.wl.init(whites)
|
||||||
|
|
||||||
|
if sh.UseWhitelist {
|
||||||
|
sh.wl.working = 1
|
||||||
} else {
|
} else {
|
||||||
sh.wl.working = 0
|
sh.wl.working = 0
|
||||||
}
|
}
|
||||||
@ -517,9 +609,9 @@ func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request)
|
|||||||
ServiceCode: sh.ServiceCode,
|
ServiceCode: sh.ServiceCode,
|
||||||
Platform: authtype,
|
Platform: authtype,
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
//Token: accesstoken,
|
Email: email,
|
||||||
Sk: newsession,
|
Sk: newsession,
|
||||||
Expired: expired,
|
Expired: expired,
|
||||||
//RefreshToken: queryvals.Get("rt"),
|
//RefreshToken: queryvals.Get("rt"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,8 +696,6 @@ func (sh *serviceDescription) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|||||||
} else {
|
} else {
|
||||||
// TODO : 세션키와 authtoken을 헤더로 받아서 accid 조회
|
// TODO : 세션키와 authtoken을 헤더로 받아서 accid 조회
|
||||||
queryvals := r.URL.Query()
|
queryvals := r.URL.Query()
|
||||||
//token := queryvals.Get("token")
|
|
||||||
token := "" // 더이상 쓰지 않는다.
|
|
||||||
sk := queryvals.Get("sk")
|
sk := queryvals.Get("sk")
|
||||||
|
|
||||||
//if len(token) == 0 || len(sk) == 0 {
|
//if len(token) == 0 || len(sk) == 0 {
|
||||||
@ -617,14 +707,53 @@ func (sh *serviceDescription) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|||||||
// TODO : 각 서버에 있는 자산? 캐릭터 정보를 보여줘야 하나. 뭘 보여줄지는 프로젝트에 문의
|
// TODO : 각 서버에 있는 자산? 캐릭터 정보를 보여줘야 하나. 뭘 보여줄지는 프로젝트에 문의
|
||||||
// 일단 서버 종류만 내려보내자
|
// 일단 서버 종류만 내려보내자
|
||||||
// 세션키가 있는지 확인
|
// 세션키가 있는지 확인
|
||||||
if _, ok := sh.auths.IsValid(sk, token); !ok {
|
if _, ok := sh.auths.IsValid(sk, ""); !ok {
|
||||||
logger.Println("sessionkey is not valid :", sk, token)
|
logger.Println("sessionkey is not valid :", sk)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
divstrptr := atomic.LoadPointer(&sh.divisionsSerialized)
|
if divname := queryvals.Get("div"); len(divname) > 0 {
|
||||||
divstr := *(*string)(divstrptr)
|
divname = strings.Trim(divname, `"`)
|
||||||
w.Write([]byte(divstr))
|
// 점검중인지 아닌지 확인
|
||||||
|
// 점검중이어도 입장이 가능한 인원이 있다.
|
||||||
|
div := sh.Divisions[divname]
|
||||||
|
if div != nil {
|
||||||
|
switch div.State {
|
||||||
|
case DivisionState_FullOpen:
|
||||||
|
w.Write([]byte(fmt.Sprintf(`{"service":"%s"}`, div.Url)))
|
||||||
|
|
||||||
|
case DivisionState_RestrictedOpen:
|
||||||
|
// 점검중인데 일부 권한을 갖고 있는 유저만 들어갈 수 있는 상태
|
||||||
|
cell := sh.auths.QuerySession(sk, "")
|
||||||
|
if cell == nil {
|
||||||
|
logger.Println("sessionkey is not valid :", sk)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if sh.wl.hasAuth(cell.ToAuthinfo().Email, cell.ToAuthinfo().Platform, whitelistAuthType_QA) {
|
||||||
|
// qa 권한이면 입장 가능
|
||||||
|
w.Write([]byte(fmt.Sprintf(`{"service":"%s"}`, div.Url)))
|
||||||
|
} else if div.Maintenance != nil {
|
||||||
|
// 권한이 없으므로 공지
|
||||||
|
w.Write([]byte(fmt.Sprintf(`{"notice":"%s"}`, div.Maintenance.link)))
|
||||||
|
} else {
|
||||||
|
logger.Println("div.Maintenance is nil :", divname)
|
||||||
|
}
|
||||||
|
|
||||||
|
case DivisionState_Maintenance:
|
||||||
|
// 점검중. 아무도 못들어감
|
||||||
|
if div.Maintenance != nil {
|
||||||
|
w.Write([]byte(fmt.Sprintf(`{"notice":"%s"}`, div.Maintenance.link)))
|
||||||
|
} else {
|
||||||
|
logger.Println("div.Maintenance is nil :", divname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
divstrptr := atomic.LoadPointer(&sh.divisionsSerialized)
|
||||||
|
divstr := *(*string)(divstrptr)
|
||||||
|
w.Write([]byte(divstr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,10 @@ type servicePipelineDocument struct {
|
|||||||
Service *serviceDescription `bson:"fullDocument"`
|
Service *serviceDescription `bson:"fullDocument"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type filePipelineDocument struct {
|
||||||
|
File *fileDocumentDesc `bson:"fullDocument"`
|
||||||
|
}
|
||||||
|
|
||||||
type whilelistPipelineDocument struct {
|
type whilelistPipelineDocument struct {
|
||||||
OperationType string `bson:"operationType"`
|
OperationType string `bson:"operationType"`
|
||||||
DocumentKey struct {
|
DocumentKey struct {
|
||||||
@ -123,6 +127,70 @@ func (mg *Maingate) watchWhitelistCollection(parentctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) watchFileCollection(parentctx context.Context, serveMux *http.ServeMux, prefix string) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error(s)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
matchStage := bson.D{
|
||||||
|
{
|
||||||
|
Key: "$match", Value: bson.D{
|
||||||
|
{Key: "operationType", Value: bson.D{
|
||||||
|
{Key: "$in", Value: bson.A{
|
||||||
|
"insert",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
projectStage := bson.D{
|
||||||
|
{
|
||||||
|
Key: "$project", Value: bson.D{
|
||||||
|
{Key: "fullDocument", Value: 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var stream *mongo.ChangeStream
|
||||||
|
var err error
|
||||||
|
var ctx context.Context
|
||||||
|
|
||||||
|
for {
|
||||||
|
if stream == nil {
|
||||||
|
stream, err = mg.mongoClient.Watch(CollectionFile, mongo.Pipeline{matchStage, projectStage}, options.ChangeStream().SetFullDocument(options.UpdateLookup))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("watchFileCollection watch failed :", err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx = context.TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
changed := stream.TryNext(ctx)
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
logger.Error("watchFileCollection stream.TryNext failed. process should be restarted! :", ctx.Err().Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !changed {
|
||||||
|
if stream.Err() != nil || stream.ID() == 0 {
|
||||||
|
logger.Error("watchServiceCollection stream error :", stream.Err())
|
||||||
|
stream.Close(ctx)
|
||||||
|
stream = nil
|
||||||
|
} else {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var data filePipelineDocument
|
||||||
|
if err := stream.Decode(&data); err == nil {
|
||||||
|
data.File.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (mg *Maingate) watchServiceCollection(parentctx context.Context, serveMux *http.ServeMux, prefix string) {
|
func (mg *Maingate) watchServiceCollection(parentctx context.Context, serveMux *http.ServeMux, prefix string) {
|
||||||
defer func() {
|
defer func() {
|
||||||
s := recover()
|
s := recover()
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ require (
|
|||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
go.mongodb.org/mongo-driver v1.11.6
|
go.mongodb.org/mongo-driver v1.11.6
|
||||||
google.golang.org/api v0.123.0
|
google.golang.org/api v0.123.0
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230524061015-e95efa06a6d4
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230528100715-93bd4f6c0bab
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -260,3 +260,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230524061015-e95efa06a6d4 h1:DFrkLvbPWqwVDU4X0QGJs2lhPduJYJU+JM/r1L2RMwo=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230524061015-e95efa06a6d4 h1:DFrkLvbPWqwVDU4X0QGJs2lhPduJYJU+JM/r1L2RMwo=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230524061015-e95efa06a6d4/go.mod h1:pw573a06qV7dP1lSyavbWmzyYAsmwtK6mdbFENbh3cs=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230524061015-e95efa06a6d4/go.mod h1:pw573a06qV7dP1lSyavbWmzyYAsmwtK6mdbFENbh3cs=
|
||||||
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230524093812-0acae49a22e7 h1:4U70jZtyMQpcF1T8z/HU8LOR2/MXoF2eJvun6lbnyuo=
|
||||||
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230524093812-0acae49a22e7/go.mod h1:5RmALPCFGFmqXa+AAPLsQaSlBVBafwX1H2CnIhsCM50=
|
||||||
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230528100715-93bd4f6c0bab h1:EMlxwDayv3rn8ttJcJuDLYoHA5odVn85+LjdAuw+2dw=
|
||||||
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230528100715-93bd4f6c0bab/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
||||||
|
|||||||
Reference in New Issue
Block a user