// authenticate is called prior to processing incoming requests. it implements the client // authentication logic, which mostly consist of validating GPG signed tokens and setting the // identity of the signer in the request context func authenticate(pass handler, adminRequired bool) handler { return func(w http.ResponseWriter, r *http.Request) { var ( err error inv mig.Investigator ) opid := getOpID(r) context.Set(r, opID, opid) context.Set(r, apiRequestCategory, RequestCategoryInvestigator) if !ctx.Authentication.Enabled { inv.Name = "authdisabled" inv.ID = 0 inv.IsAdmin = true goto authorized } if r.Header.Get("X-PGPAUTHORIZATION") == "" { inv.Name = "authmissing" inv.ID = -1 resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String())) resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: "X-PGPAUTHORIZATION header not found"}) respond(http.StatusUnauthorized, resource, w, r) return } inv, err = verifySignedToken(r.Header.Get("X-PGPAUTHORIZATION")) if err != nil { inv.Name = "authfailed" inv.ID = -1 resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String())) resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("Authorization verification failed with error '%v'", err)}) respond(http.StatusUnauthorized, resource, w, r) return } authorized: // store investigator identity in request context context.Set(r, authenticatedInvName, inv.Name) context.Set(r, authenticatedInvID, inv.ID) context.Set(r, authenticatedInvIsAdmin, inv.IsAdmin) // Validate investigator is an administrator if required if adminRequired { if !inv.IsAdmin { inv.Name = "authfailed" inv.ID = -1 ctx.Channels.Log <- mig.Log{ OpID: getOpID(r), Desc: fmt.Sprintf("Investigator '%v' %v has insufficient privileges to access API function", getInvName(r), getInvID(r)), }.Info() resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String())) resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: "Insufficient privileges"}) respond(http.StatusUnauthorized, resource, w, r) return } } // accept request pass(w, r) } }
// createInvestigator creates an investigator into the database func createInvestigator(respWriter http.ResponseWriter, request *http.Request) { var err error opid := getOpID(request) loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String()) resource := cljs.New(loc) defer func() { if e := recover(); e != nil { emsg := fmt.Sprintf("%v", e) ctx.Channels.Log <- mig.Log{OpID: opid, Desc: emsg}.Err() resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: emsg}) respond(500, resource, respWriter, request) } ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving createInvestigator()"}.Debug() }() var inv mig.Investigator err = request.ParseMultipartForm(20480) if err != nil { panic(err) } inv.Name = request.FormValue("name") if inv.Name == "" { panic("Investigator name must not be empty") } // publickey is stored in a multipart post form, extract it _, keyHeader, err := request.FormFile("publickey") if err != nil { panic(err) } keyReader, err := keyHeader.Open() if err != nil { panic(err) } inv.PublicKey, err = ioutil.ReadAll(keyReader) if err != nil { panic(err) } if len(inv.PublicKey) == 0 { panic("Investigator Public Key must not be empty") } // validate the public key and obtain a fingerprint from it inv.PGPFingerprint, err = pgp.LoadArmoredPubKey(inv.PublicKey) if err != nil { panic(err) } // create the investigator in database inv.ID, err = ctx.DB.InsertInvestigator(inv) if err != nil { panic(err) } ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "Investigator created in database"} err = resource.AddItem(cljs.Item{ Href: fmt.Sprintf("%s/investigator?investigatorid=%.0f", ctx.Server.BaseURL, inv.ID), Data: []cljs.Data{{Name: "Investigator ID " + fmt.Sprintf("%.0f", inv.ID), Value: inv}}, }) respond(201, resource, respWriter, request) }
// updateInvestigator updates the status of an investigator in database func updateInvestigator(respWriter http.ResponseWriter, request *http.Request) { var err error opid := getOpID(request) loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String()) resource := cljs.New(loc) defer func() { if e := recover(); e != nil { emsg := fmt.Sprintf("%v", e) ctx.Channels.Log <- mig.Log{OpID: opid, Desc: emsg}.Err() resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: emsg}) respond(500, resource, respWriter, request) } ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving updateInvestigator()"}.Debug() }() var inv mig.Investigator err = request.ParseForm() if err != nil { panic(err) } iid := request.FormValue("id") if iid == "" { panic("Investigator ID must not be empty") } inv.ID, err = strconv.ParseFloat(iid, 64) if err != nil { panic(err) } inv.Status = request.FormValue("status") if inv.Status == "" { panic("Investigator status must not be empty") } // create the investigator in database err = ctx.DB.UpdateInvestigatorStatus(inv) if err != nil { panic(err) } ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("Investigator %.0f status changed to %s", inv.ID, inv.Status)} err = resource.AddItem(cljs.Item{ Href: fmt.Sprintf("%s/investigator?investigatorid=%.0f", ctx.Server.BaseURL, inv.ID), Data: []cljs.Data{{Name: "Investigator ID " + fmt.Sprintf("%.0f", inv.ID), Value: inv}}, }) respond(200, resource, respWriter, request) }
// authenticate is called prior to processing incoming requests. it implements the client // authentication logic, which mostly consist of validating GPG signed tokens and setting the // identity of the signer in the request context func authenticate(pass handler) handler { return func(w http.ResponseWriter, r *http.Request) { var ( err error inv mig.Investigator ) opid := getOpID(r) context.Set(r, opID, opid) if !ctx.Authentication.Enabled { inv.Name = "authdisabled" inv.ID = 0 goto authorized } if r.Header.Get("X-PGPAUTHORIZATION") == "" { inv.Name = "authmissing" inv.ID = -1 resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String())) resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: "X-PGPAUTHORIZATION header not found"}) respond(401, resource, w, r) return } inv, err = verifySignedToken(r.Header.Get("X-PGPAUTHORIZATION")) if err != nil { inv.Name = "authfailed" inv.ID = -1 resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String())) resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("Authorization verification failed with error '%v'", err)}) respond(401, resource, w, r) return } authorized: // store investigator identity in request context context.Set(r, authenticatedInvName, inv.Name) context.Set(r, authenticatedInvID, inv.ID) // accept request pass(w, r) } }
// updateInvestigator updates the status of an investigator in database func updateInvestigator(respWriter http.ResponseWriter, request *http.Request) { var err error opid := getOpID(request) loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String()) resource := cljs.New(loc) defer func() { if e := recover(); e != nil { emsg := fmt.Sprintf("%v", e) ctx.Channels.Log <- mig.Log{OpID: opid, Desc: emsg}.Err() resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: emsg}) respond(http.StatusInternalServerError, resource, respWriter, request) } ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving updateInvestigator()"}.Debug() }() var inv mig.Investigator err = request.ParseForm() if err != nil { panic(err) } iid := request.FormValue("id") if iid == "" { panic("Investigator ID must not be empty") } inv.ID, err = strconv.ParseFloat(iid, 64) if err != nil { panic(err) } inv.Status = request.FormValue("status") isadm := request.FormValue("isadmin") if inv.Status == "" && isadm == "" { panic("No updates to the investigator were specified") } if inv.Status != "" { // update the investigator status in database err = ctx.DB.UpdateInvestigatorStatus(inv) if err != nil { panic(err) } ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("Investigator %.0f status changed to %s", inv.ID, inv.Status)} } else { switch isadm { case "true": inv.IsAdmin = true case "false": inv.IsAdmin = false // If the request is to disable the admin flag, make sure we // are not disabling the only remaining admin var cnt int cnt, err = ctx.DB.CountOtherAdminInvestigators(inv) if err != nil { panic(err) } if cnt < 1 { resource.SetError(cljs.Error{ Code: fmt.Sprintf("%.0f", opid), Message: "Will not disable last remaining administrator"}) respond(http.StatusBadRequest, resource, respWriter, request) return } default: panic("invalid value for isadmin") } err = ctx.DB.UpdateInvestigatorAdmin(inv) if err != nil { panic(err) } ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("Investigator %.0f admin changed to %v", inv.ID, inv.IsAdmin)} } err = resource.AddItem(cljs.Item{ Href: fmt.Sprintf("%s/investigator?investigatorid=%.0f", ctx.Server.BaseURL, inv.ID), Data: []cljs.Data{{Name: "Investigator ID " + fmt.Sprintf("%.0f", inv.ID), Value: inv}}, }) respond(http.StatusOK, resource, respWriter, request) }