// ServeHTTP serves an HTTP request and implements the http.Handler interface. func (root *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) ctx := ctxu.WithRequest(root.context, r) ctx, w = ctxu.WithResponseWriter(ctx, w) ctx = ctxu.WithLogger(ctx, ctxu.GetRequestLogger(ctx)) ctx = context.WithValue(ctx, "repo", vars["imageName"]) ctx = context.WithValue(ctx, "cryptoService", root.trust) defer func() { ctxu.GetResponseLogger(ctx).Info("response completed") }() if root.auth != nil { access := buildAccessRecords(vars["imageName"], root.actions...) var authCtx context.Context var err error if authCtx, err = root.auth.Authorized(ctx, access...); err != nil { if err, ok := err.(auth.Challenge); ok { err.ServeHTTP(w, r) w.WriteHeader(http.StatusUnauthorized) return } errcode.ServeJSON(w, v2.ErrorCodeUnauthorized) return } ctx = authCtx } if err := root.handler(ctx, w, r); err != nil { e := errcode.ServeJSON(w, err) if e != nil { logrus.Error(e) } return } }
// ServeHTTP serves an HTTP request and implements the http.Handler interface. func (root *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var ( err error ctx = ctxu.WithRequest(root.context, r) log = ctxu.GetRequestLogger(ctx) vars = mux.Vars(r) ) ctx, w = ctxu.WithResponseWriter(ctx, w) ctx = ctxu.WithLogger(ctx, log) ctx = context.WithValue(ctx, notary.CtxKeyCryptoSvc, root.trust) defer func() { ctxu.GetResponseLogger(ctx).Info("response completed") }() if root.auth != nil { ctx = context.WithValue(ctx, notary.CtxKeyRepo, vars["imageName"]) if ctx, err = root.doAuth(ctx, vars["imageName"], w); err != nil { // errors have already been logged/output to w inside doAuth // just return return } } if err := root.handler(ctx, w, r); err != nil { serveError(log, w, err) } }
func handleError(ctx context.Context, err error, w http.ResponseWriter) { ctx, w = context.WithResponseWriter(ctx, w) if serveErr := errcode.ServeJSON(w, err); serveErr != nil { context.GetResponseLogger(ctx).Errorf("error sending error response: %v", serveErr) return } context.GetResponseLogger(ctx).Info("application error") }
// ServeHTTP serves an HTTP request and implements the http.Handler interface. func (root *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) ctx := ctxu.WithRequest(root.context, r) log := ctxu.GetRequestLogger(ctx) ctx, w = ctxu.WithResponseWriter(ctx, w) ctx = ctxu.WithLogger(ctx, log) ctx = context.WithValue(ctx, "repo", vars["imageName"]) ctx = context.WithValue(ctx, "cryptoService", root.trust) defer func() { ctxu.GetResponseLogger(ctx).Info("response completed") }() if root.auth != nil { access := buildAccessRecords(vars["imageName"], root.actions...) var authCtx context.Context var err error if authCtx, err = root.auth.Authorized(ctx, access...); err != nil { if challenge, ok := err.(auth.Challenge); ok { // Let the challenge write the response. challenge.SetHeaders(w) if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized.WithDetail(access)); err != nil { log.Errorf("failed to serve challenge response: %s", err.Error()) } return } errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized) return } ctx = authCtx } if err := root.handler(ctx, w, r); err != nil { if httpErr, ok := err.(errcode.ErrorCoder); ok { // info level logging for non-5XX http errors httpErrCode := httpErr.ErrorCode().Descriptor().HTTPStatusCode if httpErrCode >= http.StatusInternalServerError { // error level logging for 5XX http errors log.Error(httpErr) } else { log.Info(httpErr) } } e := errcode.ServeJSON(w, err) if e != nil { log.Error(e) } return } }
// context either returns a new context or looks it up in the manager. func (cm *contextManager) context(parent context.Context, w http.ResponseWriter, r *http.Request) context.Context { cm.mu.Lock() defer cm.mu.Unlock() ctx, ok := cm.contexts[r] if ok { return ctx } if parent == nil { parent = ctxu.Background() } ctx = ctxu.WithRequest(parent, r) ctx, w = ctxu.WithResponseWriter(ctx, w) ctx = ctxu.WithLogger(ctx, ctxu.GetRequestLogger(ctx)) cm.contexts[r] = ctx return ctx }
// postToken handles authenticating the request and authorizing access to the // requested scopes. func (ts *tokenServer) postToken(ctx context.Context, w http.ResponseWriter, r *http.Request) { grantType := r.PostFormValue("grant_type") if grantType == "" { handleError(ctx, ErrorMissingRequiredField.WithDetail("missing grant_type value"), w) return } service := r.PostFormValue("service") if service == "" { handleError(ctx, ErrorMissingRequiredField.WithDetail("missing service value"), w) return } clientID := r.PostFormValue("client_id") if clientID == "" { handleError(ctx, ErrorMissingRequiredField.WithDetail("missing client_id value"), w) return } var offline bool switch r.PostFormValue("access_type") { case "", "online": case "offline": offline = true default: handleError(ctx, ErrorUnsupportedValue.WithDetail("unknown access_type value"), w) return } requestedAccessList := ResolveScopeList(ctx, r.PostFormValue("scope")) var subject string var rToken string switch grantType { case "refresh_token": rToken = r.PostFormValue("refresh_token") if rToken == "" { handleError(ctx, ErrorUnsupportedValue.WithDetail("missing refresh_token value"), w) return } rt, ok := ts.refreshCache[rToken] if !ok || rt.service != service { handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail("invalid refresh token"), w) return } subject = rt.subject case "password": ca, ok := ts.accessController.(auth.CredentialAuthenticator) if !ok { handleError(ctx, ErrorUnsupportedValue.WithDetail("password grant type not supported"), w) return } subject = r.PostFormValue("username") if subject == "" { handleError(ctx, ErrorUnsupportedValue.WithDetail("missing username value"), w) return } password := r.PostFormValue("password") if password == "" { handleError(ctx, ErrorUnsupportedValue.WithDetail("missing password value"), w) return } if err := ca.AuthenticateUser(subject, password); err != nil { handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail("invalid credentials"), w) return } default: handleError(ctx, ErrorUnsupportedValue.WithDetail("unknown grant_type value"), w) return } ctx = context.WithValue(ctx, acctSubject{}, subject) ctx = context.WithLogger(ctx, context.GetLogger(ctx, acctSubject{})) context.GetLogger(ctx).Info("authenticated client") ctx = context.WithValue(ctx, requestedAccess{}, requestedAccessList) ctx = context.WithLogger(ctx, context.GetLogger(ctx, requestedAccess{})) grantedAccessList := filterAccessList(ctx, subject, requestedAccessList) ctx = context.WithValue(ctx, grantedAccess{}, grantedAccessList) ctx = context.WithLogger(ctx, context.GetLogger(ctx, grantedAccess{})) token, err := ts.issuer.CreateJWT(subject, service, grantedAccessList) if err != nil { handleError(ctx, err, w) return } context.GetLogger(ctx).Info("authorized client") response := postTokenResponse{ Token: token, ExpiresIn: int(ts.issuer.Expiration.Seconds()), IssuedAt: time.Now().UTC().Format(time.RFC3339), Scope: ToScopeList(grantedAccessList), } if offline { rToken = newRefreshToken() ts.refreshCache[rToken] = refreshToken{ subject: subject, service: service, } } if rToken != "" { response.RefreshToken = rToken } ctx, w = context.WithResponseWriter(ctx, w) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) context.GetResponseLogger(ctx).Info("post token complete") }
// getToken handles authenticating the request and authorizing access to the // requested scopes. func (ts *tokenServer) getToken(ctx context.Context, w http.ResponseWriter, r *http.Request) { context.GetLogger(ctx).Info("getToken") params := r.URL.Query() service := params.Get("service") scopeSpecifiers := params["scope"] var offline bool if offlineStr := params.Get("offline_token"); offlineStr != "" { var err error offline, err = strconv.ParseBool(offlineStr) if err != nil { handleError(ctx, ErrorBadTokenOption.WithDetail(err), w) return } } requestedAccessList := ResolveScopeSpecifiers(ctx, scopeSpecifiers) authorizedCtx, err := ts.accessController.Authorized(ctx, requestedAccessList...) if err != nil { challenge, ok := err.(auth.Challenge) if !ok { handleError(ctx, err, w) return } // Get response context. ctx, w = context.WithResponseWriter(ctx, w) challenge.SetHeaders(w) handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail(challenge.Error()), w) context.GetResponseLogger(ctx).Info("get token authentication challenge") return } ctx = authorizedCtx username := context.GetStringValue(ctx, "auth.user.name") ctx = context.WithValue(ctx, acctSubject{}, username) ctx = context.WithLogger(ctx, context.GetLogger(ctx, acctSubject{})) context.GetLogger(ctx).Info("authenticated client") ctx = context.WithValue(ctx, requestedAccess{}, requestedAccessList) ctx = context.WithLogger(ctx, context.GetLogger(ctx, requestedAccess{})) grantedAccessList := filterAccessList(ctx, username, requestedAccessList) ctx = context.WithValue(ctx, grantedAccess{}, grantedAccessList) ctx = context.WithLogger(ctx, context.GetLogger(ctx, grantedAccess{})) token, err := ts.issuer.CreateJWT(username, service, grantedAccessList) if err != nil { handleError(ctx, err, w) return } context.GetLogger(ctx).Info("authorized client") response := tokenResponse{ Token: token, ExpiresIn: int(ts.issuer.Expiration.Seconds()), } if offline { response.RefreshToken = newRefreshToken() ts.refreshCache[response.RefreshToken] = refreshToken{ subject: username, service: service, } } ctx, w = context.WithResponseWriter(ctx, w) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) context.GetResponseLogger(ctx).Info("get token complete") }
// getToken handles authenticating the request and authorizing access to the // requested scopes. func (ts *tokenServer) getToken(ctx context.Context, w http.ResponseWriter, r *http.Request) { context.GetLogger(ctx).Info("getToken") params := r.URL.Query() service := params.Get("service") scopeSpecifiers := params["scope"] requestedAccessList := ResolveScopeSpecifiers(ctx, scopeSpecifiers) authorizedCtx, err := ts.accessController.Authorized(ctx, requestedAccessList...) if err != nil { challenge, ok := err.(auth.Challenge) if !ok { handleError(ctx, err, w) return } // Get response context. ctx, w = context.WithResponseWriter(ctx, w) challenge.SetHeaders(w) handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail(challenge.Error()), w) context.GetResponseLogger(ctx).Info("get token authentication challenge") return } ctx = authorizedCtx username := context.GetStringValue(ctx, "auth.user.name") ctx = context.WithValue(ctx, "acctSubject", username) ctx = context.WithLogger(ctx, context.GetLogger(ctx, "acctSubject")) context.GetLogger(ctx).Info("authenticated client") ctx = context.WithValue(ctx, "requestedAccess", requestedAccessList) ctx = context.WithLogger(ctx, context.GetLogger(ctx, "requestedAccess")) scopePrefix := username + "/" grantedAccessList := make([]auth.Access, 0, len(requestedAccessList)) for _, access := range requestedAccessList { if access.Type != "repository" { context.GetLogger(ctx).Debugf("Skipping unsupported resource type: %s", access.Type) continue } if !strings.HasPrefix(access.Name, scopePrefix) { context.GetLogger(ctx).Debugf("Resource scope not allowed: %s", access.Name) continue } grantedAccessList = append(grantedAccessList, access) } ctx = context.WithValue(ctx, "grantedAccess", grantedAccessList) ctx = context.WithLogger(ctx, context.GetLogger(ctx, "grantedAccess")) token, err := ts.issuer.CreateJWT(username, service, grantedAccessList) if err != nil { handleError(ctx, err, w) return } context.GetLogger(ctx).Info("authorized client") // Get response context. ctx, w = context.WithResponseWriter(ctx, w) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"token": token}) context.GetResponseLogger(ctx).Info("get token complete") }