diff --git a/core/api.go b/core/api.go index b5283f3..8cd53f4 100644 --- a/core/api.go +++ b/core/api.go @@ -86,7 +86,7 @@ func (caller apiCaller) isGlobalAdmin() bool { return false } - if _, ok := caller.admins[email.(string)]; ok { + if _, ok := caller.globalAdmins[email.(string)]; ok { return true } @@ -125,7 +125,7 @@ func (caller apiCaller) getAccessableServices() ([]*serviceDescription, []string } email = v.(string) - _, admin = caller.admins[email] + _, admin = caller.globalAdmins[email] } var output []*serviceDescription @@ -134,11 +134,9 @@ func (caller apiCaller) getAccessableServices() ([]*serviceDescription, []string if admin { output = append(output, desc) editable = append(editable, desc.ServiceName) - } else if desc.isValidAPIUser("*", email) { + } else if caller.isAdminOrValidToken(email) { output = append(output, desc) - if desc.isValidAPIUser("service", email) { - editable = append(editable, desc.ServiceName) - } + editable = append(editable, desc.ServiceName) } } @@ -149,60 +147,49 @@ func (caller apiCaller) getAccessableServices() ([]*serviceDescription, []string return output, editable } -func (caller apiCaller) isValidUser(service any, category string) (valid bool, admin bool) { +func (caller apiCaller) isAdmin(service any) bool { if *noauth { - return true, true + return true } v, ok := caller.userinfo["email"] if !ok { logger.Println("isVaidUser failed. email is missing :", caller.userinfo) - return false, false + return false } email := v.(string) - if _, ok := caller.admins[email]; ok { - return true, true + if _, ok := caller.globalAdmins[email]; ok { + return true } svcdesc := caller.mg.services.get(service) if svcdesc == nil { logger.Println("isVaidUser failed. service is missing :", service) - return false, false + return false } - return svcdesc.isValidAPIUser(category, email), false + return svcdesc.isAdmin(email) +} + +func (caller apiCaller) isAdminOrValidToken(service any) bool { + if caller.isAdmin(service) { + return true + } + + sh := caller.mg.services.get(service) + if sh == nil { + return false + } + + return sh.isValidToken(caller.apiToken) } func (caller apiCaller) filesAPI(w http.ResponseWriter, r *http.Request) error { if r.Method == "GET" { - hasAuth := caller.isGlobalAdmin() - var email string - if !*noauth { - v, ok := caller.userinfo["email"] - if !ok { - return nil - } - - email = v.(string) - _, hasAuth = caller.admins[email] - } - servicename := r.FormValue("service") - sh := caller.mg.services.get(servicename) - if sh == nil { - w.WriteHeader(http.StatusBadRequest) - return nil - } - - if !hasAuth { - if hasAuth = sh.isValidAPIUser("maintenance", email); !hasAuth { - hasAuth = sh.isValidAPIUser("service", email) - } - } - - if !hasAuth { - w.WriteHeader(http.StatusBadRequest) + if !caller.isAdminOrValidToken(servicename) { + w.WriteHeader(http.StatusUnauthorized) return nil } @@ -228,6 +215,11 @@ func (caller apiCaller) filesAPI(w http.ResponseWriter, r *http.Request) error { return nil } + if !caller.isAdminOrValidToken(servicename) { + w.WriteHeader(http.StatusUnauthorized) + return nil + } + _, err := caller.mg.mongoClient.Delete(CollectionFile, bson.M{ "service": servicename, "key": key, @@ -309,14 +301,12 @@ func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) err 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 { + if !caller.isAdminOrValidToken(service) { + logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo) + w.WriteHeader(http.StatusUnauthorized) + return nil + } all, err := mg.mongoClient.FindAll(CollectionWhitelist, bson.M{ "service": service, @@ -344,9 +334,10 @@ func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) err if err := json.Unmarshal(body, &member); err != nil { return err } - if valid, _ := caller.isValidUser(member.Service, "whitelist"); !valid { + + if !caller.isAdminOrValidToken(member.Service) { logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo) - w.WriteHeader(http.StatusBadRequest) + w.WriteHeader(http.StatusUnauthorized) return nil } @@ -391,16 +382,13 @@ func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error if r.Method == "GET" { name := queryvals.Get("name") if len(name) > 0 { - if valid, _ := caller.isValidUser(name, "*"); !valid { + if !caller.isAdminOrValidToken(name) { 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) - } - + w.Header().Add("MG-X-SERVICE-EDITABLE", name) serptr := atomic.LoadPointer(&mg.services.get(name).serviceSerialized) w.Write(*(*[]byte)(serptr)) } else { @@ -421,7 +409,7 @@ func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error w.WriteHeader(http.StatusBadRequest) return nil } - } else if valid, _ := caller.isValidUser(service.Id, "service"); !valid { + } else if !caller.isAdminOrValidToken(service.Id) { logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo) w.WriteHeader(http.StatusBadRequest) return nil @@ -461,15 +449,11 @@ func (caller apiCaller) maintenanceAPI(w http.ResponseWriter, r *http.Request) e if r.Method == "GET" { name := queryvals.Get("name") if len(name) > 0 { - if valid, _ := caller.isValidUser(name, "*"); !valid { - w.WriteHeader(http.StatusBadRequest) + if !caller.isAdminOrValidToken(name) { + w.WriteHeader(http.StatusUnauthorized) return nil } - - if valid, admin := caller.isValidUser(name, "maintenance"); valid || admin { - w.Header().Add("MG-X-SERVICE-EDITABLE", name) - } - + w.Header().Add("MG-X-SERVICE-EDITABLE", name) serptr := atomic.LoadPointer(&mg.services.get(name).divisionsSerialized) w.Write(*(*[]byte)(serptr)) } else { @@ -477,7 +461,7 @@ func (caller apiCaller) maintenanceAPI(w http.ResponseWriter, r *http.Request) e } } else if r.Method == "POST" { servicename := queryvals.Get("name") - if valid, _ := caller.isValidUser(servicename, "service"); !valid { + if !caller.isAdminOrValidToken(servicename) { logger.Println("maintenanceAPI failed. not vaild user :", r.Method, caller.userinfo) w.WriteHeader(http.StatusBadRequest) return nil @@ -514,9 +498,9 @@ func (caller apiCaller) accountAPI(w http.ResponseWriter, r *http.Request) error return nil } - if valid, _ := caller.isValidUser(service, "account"); !valid { + if !caller.isAdminOrValidToken(service) { logger.Println("accountAPI failed. not vaild user :", r.Method, caller.userinfo) - w.WriteHeader(http.StatusBadRequest) + w.WriteHeader(http.StatusUnauthorized) return nil } @@ -641,9 +625,10 @@ func (caller apiCaller) configAPI(w http.ResponseWriter, r *http.Request) error var noauth = flag.Bool("noauth", false, "") type apiCaller struct { - userinfo map[string]any - admins map[string]bool - mg *Maingate + userinfo map[string]any + globalAdmins map[string]bool + mg *Maingate + apiToken primitive.ObjectID } func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) { @@ -704,11 +689,24 @@ func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) { } } + apiToken := r.Header.Get("MG-X-API-TOKEN") + var apiTokenObj primitive.ObjectID + if len(apiToken) > 0 { + obj, err := primitive.ObjectIDFromHex(apiToken) + if err != nil { + logger.Error(err) + w.WriteHeader(http.StatusBadRequest) + return + } + apiTokenObj = obj + } + logger.Println("api call :", r.URL.Path, r.Method, r.URL.Query(), userinfo) caller := apiCaller{ - userinfo: userinfo, - admins: adminsptr.emails, - mg: mg, + userinfo: userinfo, + globalAdmins: adminsptr.emails, + mg: mg, + apiToken: apiTokenObj, } var err error diff --git a/core/service.go b/core/service.go index 0d66e2e..f5598a1 100644 --- a/core/service.go +++ b/core/service.go @@ -138,7 +138,7 @@ type serviceDescription struct { ServiceDescriptionSummary `bson:",inline" json:",inline"` Divisions map[string]*Division `bson:"divisions" json:"divisions"` ServerApiTokens []primitive.ObjectID `bson:"api_tokens" json:"api_tokens"` - ApiUsers map[string][]string `bson:"api_users" json:"api_users"` + Admins []string `bson:"admins" json:"admins"` auths *common.AuthCollection wl whitelist @@ -150,13 +150,26 @@ type serviceDescription struct { updateUserinfo func(info usertokeninfo) (bool, string, string) getProviderInfo func(platform string, uid string) (string, string, error) - apiUsers unsafe.Pointer + admins unsafe.Pointer divisionsForUsersSerialized unsafe.Pointer divisionsSerialized unsafe.Pointer serviceSerialized unsafe.Pointer serviceSummarySerialized unsafe.Pointer } +func (sh *serviceDescription) isValidToken(apiToken primitive.ObjectID) bool { + if apiToken.IsZero() { + return false + } + + for _, test := range sh.ServerApiTokens { + if test == apiToken { + return true + } + } + return false +} + func (sh *serviceDescription) readProfile(authtype string, id string, binfo string) (email string, err error) { defer func() { s := recover() @@ -254,25 +267,7 @@ func (sh *serviceDescription) prepare(mg *Maingate) error { } sh.wl.init(whites) - - if len(sh.ApiUsers) == 0 { - sh.ApiUsers = map[string][]string{ - "service": {}, - "whitelist": {}, - "account": {}, - "maintenance": {}, - } - } - parsedUsers := make(map[string]map[string]bool) - for cat, users := range sh.ApiUsers { - catusers := make(map[string]bool) - for _, user := range users { - catusers[user] = true - } - parsedUsers[cat] = catusers - } - - sh.apiUsers = unsafe.Pointer(&parsedUsers) + sh.admins = unsafe.Pointer(&sh.Admins) for _, keyid := range sh.ServerApiTokens { mg.apiTokenToService.add(keyid.Hex(), sh.ServiceCode) } @@ -283,7 +278,7 @@ func (sh *serviceDescription) prepare(mg *Maingate) error { btsum, _ := json.Marshal(sh.ServiceDescriptionSummary) atomic.StorePointer(&sh.serviceSummarySerialized, unsafe.Pointer(&btsum)) - logger.Println("service is ready :", sh.ServiceName, sh.ServiceCode, sh.ApiUsers, string(divmarshaled)) + logger.Println("service is ready :", sh.ServiceName, sh.ServiceCode, sh.Admins, string(divmarshaled)) return nil } @@ -410,30 +405,15 @@ func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) { logger.Println("link success :", r.URL.Query()) } -func (sh *serviceDescription) isValidAPIUser(category string, email string) bool { - ptr := atomic.LoadPointer(&sh.apiUsers) - catusers := *(*map[string]map[string]bool)(ptr) +func (sh *serviceDescription) isAdmin(email string) bool { + ptr := atomic.LoadPointer(&sh.admins) + admins := *(*[]string)(ptr) - if category == "*" { - for _, users := range catusers { - if _, ok := users[email]; ok { - return true - } - } - logger.Println("isValidAPIUser failed. email is not allowed :", category, email, catusers) - return false - } - - if users, ok := catusers[category]; ok { - if _, ok := users[email]; ok { + for _, a := range admins { + if a == email { return true } - - logger.Println("isValidAPIUser failed. email is not allowed :", category, email, users) - return false } - - logger.Println("isValidAPIUser failed. category is missing :", category) return false } diff --git a/core/watch.go b/core/watch.go index 118ebae..d8cd4f2 100644 --- a/core/watch.go +++ b/core/watch.go @@ -303,7 +303,7 @@ func (mg *Maingate) watchServiceCollection(parentctx context.Context, serveMux * if old := mg.services.get(data.Service.ServiceName); old != nil { atomic.SwapPointer(&old.divisionsForUsersSerialized, data.Service.divisionsForUsersSerialized) atomic.SwapPointer(&old.divisionsSerialized, data.Service.divisionsSerialized) - atomic.SwapPointer(&old.apiUsers, data.Service.apiUsers) + atomic.SwapPointer(&old.admins, data.Service.admins) atomic.SwapPointer(&old.serviceSerialized, data.Service.serviceSerialized) atomic.SwapPointer(&old.serviceSummarySerialized, data.Service.serviceSummarySerialized)