maingate 이전
This commit is contained in:
472
core/api.go
Normal file
472
core/api.go
Normal file
@ -0,0 +1,472 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func (caller apiCaller) isGlobalAdmin() bool {
|
||||
if *noauth {
|
||||
return true
|
||||
}
|
||||
|
||||
email, ok := caller.userinfo["email"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := caller.admins[email.(string)]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (caller apiCaller) writeAccessableServices(w http.ResponseWriter) {
|
||||
services, editable := caller.getAccessableServices()
|
||||
for _, r := range editable {
|
||||
w.Header().Add("MG-X-SERVICE-EDITABLE", r)
|
||||
}
|
||||
|
||||
w.Write([]byte("{"))
|
||||
start := true
|
||||
for _, v := range services {
|
||||
if !start {
|
||||
w.Write([]byte(","))
|
||||
}
|
||||
w.Write([]byte(fmt.Sprintf(`"%s":`, v.ServiceName)))
|
||||
serptr := atomic.LoadPointer(&v.serviceSerialized)
|
||||
w.Write(*(*[]byte)(serptr))
|
||||
start = false
|
||||
}
|
||||
w.Write([]byte("}"))
|
||||
}
|
||||
|
||||
func (caller apiCaller) getAccessableServices() ([]*serviceDescription, []string) {
|
||||
allservices := caller.mg.services.all()
|
||||
v, ok := caller.userinfo["email"]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
email := v.(string)
|
||||
_, admin := caller.admins[email]
|
||||
|
||||
var output []*serviceDescription
|
||||
var editable []string
|
||||
for _, desc := range allservices {
|
||||
if admin {
|
||||
output = append(output, desc)
|
||||
editable = append(editable, desc.ServiceName)
|
||||
} else if desc.isValidAPIUser("*", email) {
|
||||
output = append(output, desc)
|
||||
if desc.isValidAPIUser("service", email) {
|
||||
editable = append(editable, desc.ServiceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(output, func(i, j int) bool {
|
||||
return output[i].ServiceName < output[j].ServiceName
|
||||
})
|
||||
|
||||
return output, editable
|
||||
}
|
||||
|
||||
func (caller apiCaller) isValidUser(service any, category string) (valid bool, admin bool) {
|
||||
if *noauth {
|
||||
return true, true
|
||||
}
|
||||
|
||||
v, ok := caller.userinfo["email"]
|
||||
if !ok {
|
||||
logger.Println("isVaidUser failed. email is missing :", caller.userinfo)
|
||||
return false, false
|
||||
}
|
||||
|
||||
email := v.(string)
|
||||
if _, ok := caller.admins[email]; ok {
|
||||
return true, true
|
||||
}
|
||||
|
||||
svcdesc := caller.mg.services.get(service)
|
||||
if svcdesc == nil {
|
||||
logger.Println("isVaidUser failed. service is missing :", service)
|
||||
return false, false
|
||||
}
|
||||
|
||||
return svcdesc.isValidAPIUser(category, email), false
|
||||
}
|
||||
|
||||
func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) error {
|
||||
mg := caller.mg
|
||||
queryvals := r.URL.Query()
|
||||
if r.Method == "GET" {
|
||||
service := queryvals.Get("service")
|
||||
|
||||
if valid, _ := caller.isValidUser(service, "whitelist"); !valid {
|
||||
logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(service) > 0 {
|
||||
|
||||
all, err := mg.mongoClient.FindAll(CollectionWhitelist, bson.M{
|
||||
"service": service,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(all) > 0 {
|
||||
allraw, _ := json.Marshal(all)
|
||||
w.Write(allraw)
|
||||
}
|
||||
} else {
|
||||
logger.Println("service param is missing")
|
||||
}
|
||||
} else if r.Method == "PUT" {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
var member whitelistmember
|
||||
if err := json.Unmarshal(body, &member); err != nil {
|
||||
return err
|
||||
}
|
||||
if valid, _ := caller.isValidUser(member.Service, "whitelist"); !valid {
|
||||
logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
member.Expired = 0
|
||||
|
||||
_, _, err := mg.mongoClient.Update(CollectionWhitelist, bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
}, bson.M{
|
||||
"$set": &member,
|
||||
}, options.Update().SetUpsert(true))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if r.Method == "DELETE" {
|
||||
id := queryvals.Get("id")
|
||||
if len(id) == 0 {
|
||||
return errors.New("id param is missing")
|
||||
}
|
||||
idobj, err := primitive.ObjectIDFromHex(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = mg.mongoClient.Update(CollectionWhitelist, bson.M{
|
||||
"_id": idobj,
|
||||
}, bson.M{
|
||||
"$currentDate": bson.M{
|
||||
"_ts": bson.M{"$type": "date"},
|
||||
},
|
||||
}, options.Update().SetUpsert(false))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error {
|
||||
mg := caller.mg
|
||||
queryvals := r.URL.Query()
|
||||
if r.Method == "GET" {
|
||||
name := queryvals.Get("name")
|
||||
if len(name) > 0 {
|
||||
if valid, _ := caller.isValidUser(name, "*"); !valid {
|
||||
logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
if valid, admin := caller.isValidUser(name, "service"); valid || admin {
|
||||
w.Header().Add("MG-X-SERVICE-EDITABLE", name)
|
||||
}
|
||||
|
||||
serptr := atomic.LoadPointer(&mg.services.get(name).serviceSerialized)
|
||||
w.Write(*(*[]byte)(serptr))
|
||||
} else {
|
||||
caller.writeAccessableServices(w)
|
||||
}
|
||||
} else if r.Method == "POST" {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
var service serviceDescription
|
||||
if err := json.Unmarshal(body, &service); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if service.Id.IsZero() {
|
||||
if caller.isGlobalAdmin() {
|
||||
service.Id = primitive.NewObjectID()
|
||||
} else {
|
||||
logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
} else if valid, _ := caller.isValidUser(service.Id, "service"); !valid {
|
||||
logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
filter := bson.M{"_id": service.Id}
|
||||
success, _, err := mg.mongoClient.Update(CollectionService, filter, bson.M{
|
||||
"$set": &service,
|
||||
}, options.Update().SetUpsert(true))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !success {
|
||||
logger.Println("serviceAPI failed. not vaild user :", caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (caller apiCaller) accountAPI(w http.ResponseWriter, r *http.Request) error {
|
||||
mg := caller.mg
|
||||
queryvals := r.URL.Query()
|
||||
if r.Method == "GET" {
|
||||
service := queryvals.Get("service")
|
||||
if len(service) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if valid, _ := caller.isValidUser(service, "account"); !valid {
|
||||
logger.Println("accountAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
var accdoc primitive.M
|
||||
if v := queryvals.Get("accid"); len(v) == 0 {
|
||||
email := queryvals.Get("email")
|
||||
platform := queryvals.Get("platform")
|
||||
if len(email) == 0 || len(platform) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
found, err := mg.mongoClient.FindOne(CollectionLink, bson.M{
|
||||
"email": email,
|
||||
"platform": platform,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if found == nil {
|
||||
return nil
|
||||
}
|
||||
if idobj, ok := found["_id"]; ok {
|
||||
svcdoc, err := mg.mongoClient.FindOne(common.CollectionName(service), bson.M{
|
||||
"_id": idobj,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if svcdoc != nil {
|
||||
found["accid"] = svcdoc["accid"]
|
||||
}
|
||||
|
||||
accdoc = found
|
||||
}
|
||||
} else {
|
||||
accid, err := primitive.ObjectIDFromHex(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
svcdoc, err := mg.mongoClient.FindOne(common.CollectionName(service), bson.M{
|
||||
"accid": accid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
found, err := mg.mongoClient.FindOne(CollectionLink, bson.M{
|
||||
"_id": svcdoc["_id"],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found != nil {
|
||||
found["accid"] = accid
|
||||
}
|
||||
accdoc = found
|
||||
}
|
||||
|
||||
if accdoc != nil {
|
||||
accdoc["code"] = service
|
||||
delete(accdoc, "uid")
|
||||
delete(accdoc, "_id")
|
||||
|
||||
var bi blockinfo
|
||||
if err := mg.mongoClient.FindOneAs(CollectionBlock, bson.M{
|
||||
"code": service,
|
||||
"accid": accdoc["accid"],
|
||||
}, &bi); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bi.Start.Time().IsZero() && bi.End.Time().After(time.Now().UTC()) {
|
||||
accdoc["blocked"] = bi
|
||||
}
|
||||
return json.NewEncoder(w).Encode(accdoc)
|
||||
}
|
||||
} else if r.Method == "POST" {
|
||||
var account struct {
|
||||
Code string
|
||||
Accid string
|
||||
Blocked blockinfo
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(body, &account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accid, _ := primitive.ObjectIDFromHex(account.Accid)
|
||||
if !account.Blocked.Start.Time().IsZero() && account.Blocked.Start.Time().After(time.Now().UTC()) {
|
||||
if _, _, err := mg.mongoClient.Update(CollectionBlock, bson.M{
|
||||
"code": account.Code,
|
||||
"accid": accid,
|
||||
}, bson.M{
|
||||
"$set": account.Blocked,
|
||||
}, options.Update().SetUpsert(true)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var errApiTokenMissing = errors.New("mg-x-api-token is missing")
|
||||
|
||||
func (caller apiCaller) configAPI(w http.ResponseWriter, r *http.Request) error {
|
||||
mg := caller.mg
|
||||
apitoken := r.Header.Get("MG-X-API-TOKEN")
|
||||
if len(apitoken) == 0 {
|
||||
return errApiTokenMissing
|
||||
}
|
||||
|
||||
if _, exists := mg.apiTokenToService.get(apitoken); !exists {
|
||||
return fmt.Errorf("mg-x-api-token is not valid : %s", apitoken)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var noauth = flag.Bool("noauth", false, "")
|
||||
|
||||
type apiCaller struct {
|
||||
userinfo map[string]any
|
||||
admins map[string]bool
|
||||
mg *Maingate
|
||||
}
|
||||
|
||||
func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error(s)
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
r.Body.Close()
|
||||
}()
|
||||
|
||||
var userinfo map[string]any
|
||||
|
||||
if !*noauth {
|
||||
authheader := r.Header.Get("Authorization")
|
||||
if len(authheader) == 0 {
|
||||
logger.Println("Authorization header is not valid :", authheader)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", "https://graph.microsoft.com/oidc/userinfo", nil)
|
||||
req.Header.Add("Authorization", authheader)
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.Println("graph microsoft api call failed :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
if err = json.Unmarshal(raw, &userinfo); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, expired := userinfo["error"]; expired {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ptr := atomic.LoadPointer(&mg.admins)
|
||||
adminsptr := (*globalAdmins)(ptr)
|
||||
|
||||
if adminsptr.modtime != common.ConfigModTime() {
|
||||
var config globalAdmins
|
||||
if err := common.LoadConfig(&config); err == nil {
|
||||
config.parse()
|
||||
adminsptr = &config
|
||||
atomic.StorePointer(&mg.admins, unsafe.Pointer(adminsptr))
|
||||
}
|
||||
}
|
||||
|
||||
logger.Println("api call :", r.URL.Path, r.Method, r.URL.Query(), userinfo)
|
||||
caller := apiCaller{
|
||||
userinfo: userinfo,
|
||||
admins: adminsptr.emails,
|
||||
mg: mg,
|
||||
}
|
||||
|
||||
var err error
|
||||
if strings.HasSuffix(r.URL.Path, "/service") {
|
||||
err = caller.serviceAPI(w, r)
|
||||
} else if strings.HasSuffix(r.URL.Path, "/whitelist") {
|
||||
err = caller.whitelistAPI(w, r)
|
||||
} else if strings.HasSuffix(r.URL.Path, "/config") {
|
||||
err = caller.configAPI(w, r)
|
||||
} else if strings.HasSuffix(r.URL.Path, "/account") {
|
||||
err = caller.accountAPI(w, r)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user