// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Error error
//
// swagger:model Error
type Error struct {
// code
Code int64 `json:"code,omitempty"`
// message
Message string `json:"message,omitempty"`
}
// Validate validates this error
func (m *Error) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this error based on context it is used
func (m *Error) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Error) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Error) UnmarshalBinary(b []byte) error {
var res Error
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// Repo repo
//
// swagger:model Repo
type Repo struct {
// respository that was analyzed
Name string `json:"name,omitempty"`
// SHA1 value of the analyzed commit expressed as hexadecimal
// Pattern: ^[0-9a-fA-F]{40}$
Commit string `json:"commit,omitempty"`
}
// Validate validates this repo
func (m *Repo) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCommit(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Repo) validateCommit(formats strfmt.Registry) error {
if swag.IsZero(m.Commit) { // not required
return nil
}
if err := validate.Pattern("commit", "body", m.Commit, `^[0-9a-fA-F]{40}$`); err != nil {
return err
}
return nil
}
// ContextValidate validates this repo based on context it is used
func (m *Repo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Repo) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Repo) UnmarshalBinary(b []byte) error {
var res Repo
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ScorecardCheck scorecard check
//
// swagger:model ScorecardCheck
type ScorecardCheck struct {
// name
Name string `json:"name,omitempty"`
// score
Score int64 `json:"score"`
// reason
Reason string `json:"reason,omitempty"`
// details
Details []string `json:"details"`
// documentation
Documentation *ScorecardCheckDocumentation `json:"documentation,omitempty"`
}
// Validate validates this scorecard check
func (m *ScorecardCheck) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateDocumentation(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ScorecardCheck) validateDocumentation(formats strfmt.Registry) error {
if swag.IsZero(m.Documentation) { // not required
return nil
}
if m.Documentation != nil {
if err := m.Documentation.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("documentation")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("documentation")
}
return err
}
}
return nil
}
// ContextValidate validate this scorecard check based on the context it is used
func (m *ScorecardCheck) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateDocumentation(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ScorecardCheck) contextValidateDocumentation(ctx context.Context, formats strfmt.Registry) error {
if m.Documentation != nil {
if err := m.Documentation.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("documentation")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("documentation")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ScorecardCheck) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ScorecardCheck) UnmarshalBinary(b []byte) error {
var res ScorecardCheck
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// ScorecardCheckDocumentation scorecard check documentation
//
// swagger:model ScorecardCheckDocumentation
type ScorecardCheckDocumentation struct {
// short
Short string `json:"short,omitempty"`
// url
URL string `json:"url,omitempty"`
}
// Validate validates this scorecard check documentation
func (m *ScorecardCheckDocumentation) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this scorecard check documentation based on context it is used
func (m *ScorecardCheckDocumentation) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *ScorecardCheckDocumentation) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ScorecardCheckDocumentation) UnmarshalBinary(b []byte) error {
var res ScorecardCheckDocumentation
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ScorecardResult scorecard result
//
// swagger:model ScorecardResult
type ScorecardResult struct {
// date
Date string `json:"date,omitempty"`
// repo
Repo *Repo `json:"repo,omitempty"`
// scorecard
Scorecard *ScorecardVersion `json:"scorecard,omitempty"`
// Aggregate score of the repository
Score float64 `json:"score"`
// checks
Checks []*ScorecardCheck `json:"checks"`
// metadata
Metadata string `json:"metadata,omitempty"`
}
// Validate validates this scorecard result
func (m *ScorecardResult) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateRepo(formats); err != nil {
res = append(res, err)
}
if err := m.validateScorecard(formats); err != nil {
res = append(res, err)
}
if err := m.validateChecks(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ScorecardResult) validateRepo(formats strfmt.Registry) error {
if swag.IsZero(m.Repo) { // not required
return nil
}
if m.Repo != nil {
if err := m.Repo.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("repo")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("repo")
}
return err
}
}
return nil
}
func (m *ScorecardResult) validateScorecard(formats strfmt.Registry) error {
if swag.IsZero(m.Scorecard) { // not required
return nil
}
if m.Scorecard != nil {
if err := m.Scorecard.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("scorecard")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("scorecard")
}
return err
}
}
return nil
}
func (m *ScorecardResult) validateChecks(formats strfmt.Registry) error {
if swag.IsZero(m.Checks) { // not required
return nil
}
for i := 0; i < len(m.Checks); i++ {
if swag.IsZero(m.Checks[i]) { // not required
continue
}
if m.Checks[i] != nil {
if err := m.Checks[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("checks" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("checks" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this scorecard result based on the context it is used
func (m *ScorecardResult) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateRepo(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateScorecard(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateChecks(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ScorecardResult) contextValidateRepo(ctx context.Context, formats strfmt.Registry) error {
if m.Repo != nil {
if err := m.Repo.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("repo")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("repo")
}
return err
}
}
return nil
}
func (m *ScorecardResult) contextValidateScorecard(ctx context.Context, formats strfmt.Registry) error {
if m.Scorecard != nil {
if err := m.Scorecard.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("scorecard")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("scorecard")
}
return err
}
}
return nil
}
func (m *ScorecardResult) contextValidateChecks(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Checks); i++ {
if m.Checks[i] != nil {
if err := m.Checks[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("checks" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("checks" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ScorecardResult) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ScorecardResult) UnmarshalBinary(b []byte) error {
var res ScorecardResult
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// ScorecardVersion scorecard version
//
// swagger:model ScorecardVersion
type ScorecardVersion struct {
// Scorecard version used for this analysis
Version string `json:"version,omitempty"`
// SHA1 value of the Scorecard commit used for analysis
// Pattern: ^[0-9a-fA-F]{40}$
Commit string `json:"commit,omitempty"`
}
// Validate validates this scorecard version
func (m *ScorecardVersion) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCommit(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ScorecardVersion) validateCommit(formats strfmt.Registry) error {
if swag.IsZero(m.Commit) { // not required
return nil
}
if err := validate.Pattern("commit", "body", m.Commit, `^[0-9a-fA-F]{40}$`); err != nil {
return err
}
return nil
}
// ContextValidate validates this scorecard version based on context it is used
func (m *ScorecardVersion) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *ScorecardVersion) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ScorecardVersion) UnmarshalBinary(b []byte) error {
var res ScorecardVersion
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// VerifiedScorecardResult verified scorecard result
//
// swagger:model VerifiedScorecardResult
type VerifiedScorecardResult struct {
// access token
AccessToken string `json:"accessToken,omitempty"`
// branch
Branch string `json:"branch,omitempty"`
// result
Result string `json:"result,omitempty"`
// tlog index
TlogIndex int64 `json:"tlogIndex,omitempty"`
}
// Validate validates this verified scorecard result
func (m *VerifiedScorecardResult) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this verified scorecard result based on context it is used
func (m *VerifiedScorecardResult) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *VerifiedScorecardResult) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *VerifiedScorecardResult) UnmarshalBinary(b []byte) error {
var res VerifiedScorecardResult
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package badge
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// GetBadgeHandlerFunc turns a function with the right signature into a get badge handler
type GetBadgeHandlerFunc func(GetBadgeParams) middleware.Responder
// Handle executing the request and returning a response
func (fn GetBadgeHandlerFunc) Handle(params GetBadgeParams) middleware.Responder {
return fn(params)
}
// GetBadgeHandler interface for that can handle valid get badge params
type GetBadgeHandler interface {
Handle(GetBadgeParams) middleware.Responder
}
// NewGetBadge creates a new http.Handler for the get badge operation
func NewGetBadge(ctx *middleware.Context, handler GetBadgeHandler) *GetBadge {
return &GetBadge{Context: ctx, Handler: handler}
}
/*
GetBadge swagger:route GET /projects/{platform}/{org}/{repo}/badge badge getBadge
Get a repository's Scorecard badge
*/
type GetBadge struct {
Context *middleware.Context
Handler GetBadgeHandler
}
func (o *GetBadge) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewGetBadgeParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package badge
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// NewGetBadgeParams creates a new GetBadgeParams object
// with the default values initialized.
func NewGetBadgeParams() GetBadgeParams {
var (
// initialize parameters with default values
styleDefault = string("flat")
)
return GetBadgeParams{
Style: &styleDefault,
}
}
// GetBadgeParams contains all the bound params for the get badge operation
// typically these are obtained from a http.Request
//
// swagger:parameters getBadge
type GetBadgeParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Name of the owner/organization of the repository
Required: true
In: path
*/
Org string
/*VCS platform. eg. github.com
Required: true
In: path
*/
Platform string
/*Name of the repository
Required: true
In: path
*/
Repo string
/*Style to render the badge
In: query
Default: "flat"
*/
Style *string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewGetBadgeParams() beforehand.
func (o *GetBadgeParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
rOrg, rhkOrg, _ := route.Params.GetOK("org")
if err := o.bindOrg(rOrg, rhkOrg, route.Formats); err != nil {
res = append(res, err)
}
rPlatform, rhkPlatform, _ := route.Params.GetOK("platform")
if err := o.bindPlatform(rPlatform, rhkPlatform, route.Formats); err != nil {
res = append(res, err)
}
rRepo, rhkRepo, _ := route.Params.GetOK("repo")
if err := o.bindRepo(rRepo, rhkRepo, route.Formats); err != nil {
res = append(res, err)
}
qStyle, qhkStyle, _ := qs.GetOK("style")
if err := o.bindStyle(qStyle, qhkStyle, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindOrg binds and validates parameter Org from path.
func (o *GetBadgeParams) bindOrg(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Org = raw
return nil
}
// bindPlatform binds and validates parameter Platform from path.
func (o *GetBadgeParams) bindPlatform(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Platform = raw
return nil
}
// bindRepo binds and validates parameter Repo from path.
func (o *GetBadgeParams) bindRepo(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Repo = raw
return nil
}
// bindStyle binds and validates parameter Style from query.
func (o *GetBadgeParams) bindStyle(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewGetBadgeParams()
return nil
}
o.Style = &raw
if err := o.validateStyle(formats); err != nil {
return err
}
return nil
}
// validateStyle carries on validations for parameter Style
func (o *GetBadgeParams) validateStyle(formats strfmt.Registry) error {
if err := validate.EnumCase("style", "query", *o.Style, []interface{}{"plastic", "flat", "flat-square", "for-the-badge", "social"}, true); err != nil {
return err
}
return nil
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package badge
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/ossf/scorecard-webapp/app/generated/models"
)
// GetBadgeFoundCode is the HTTP code returned for type GetBadgeFound
const GetBadgeFoundCode int = 302
/*
GetBadgeFound Scorecard badge for the repository
swagger:response getBadgeFound
*/
type GetBadgeFound struct {
}
// NewGetBadgeFound creates GetBadgeFound with default headers values
func NewGetBadgeFound() *GetBadgeFound {
return &GetBadgeFound{}
}
// WriteResponse to the client
func (o *GetBadgeFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(302)
}
/*
GetBadgeDefault There was an internal error in the server while processing the request
swagger:response getBadgeDefault
*/
type GetBadgeDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewGetBadgeDefault creates GetBadgeDefault with default headers values
func NewGetBadgeDefault(code int) *GetBadgeDefault {
if code <= 0 {
code = 500
}
return &GetBadgeDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get badge default response
func (o *GetBadgeDefault) WithStatusCode(code int) *GetBadgeDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get badge default response
func (o *GetBadgeDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get badge default response
func (o *GetBadgeDefault) WithPayload(payload *models.Error) *GetBadgeDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get badge default response
func (o *GetBadgeDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetBadgeDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package badge
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// GetBadgeURL generates an URL for the get badge operation
type GetBadgeURL struct {
Org string
Platform string
Repo string
Style *string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetBadgeURL) WithBasePath(bp string) *GetBadgeURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetBadgeURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *GetBadgeURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/projects/{platform}/{org}/{repo}/badge"
org := o.Org
if org != "" {
_path = strings.Replace(_path, "{org}", org, -1)
} else {
return nil, errors.New("org is required on GetBadgeURL")
}
platform := o.Platform
if platform != "" {
_path = strings.Replace(_path, "{platform}", platform, -1)
} else {
return nil, errors.New("platform is required on GetBadgeURL")
}
repo := o.Repo
if repo != "" {
_path = strings.Replace(_path, "{repo}", repo, -1)
} else {
return nil, errors.New("repo is required on GetBadgeURL")
}
_basePath := o._basePath
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
var styleQ string
if o.Style != nil {
styleQ = *o.Style
}
if styleQ != "" {
qs.Set("style", styleQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *GetBadgeURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *GetBadgeURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *GetBadgeURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on GetBadgeURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on GetBadgeURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *GetBadgeURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package results
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// GetResultHandlerFunc turns a function with the right signature into a get result handler
type GetResultHandlerFunc func(GetResultParams) middleware.Responder
// Handle executing the request and returning a response
func (fn GetResultHandlerFunc) Handle(params GetResultParams) middleware.Responder {
return fn(params)
}
// GetResultHandler interface for that can handle valid get result params
type GetResultHandler interface {
Handle(GetResultParams) middleware.Responder
}
// NewGetResult creates a new http.Handler for the get result operation
func NewGetResult(ctx *middleware.Context, handler GetResultHandler) *GetResult {
return &GetResult{Context: ctx, Handler: handler}
}
/*
GetResult swagger:route GET /projects/{platform}/{org}/{repo} results getResult
Get a repository's ScorecardResult
*/
type GetResult struct {
Context *middleware.Context
Handler GetResultHandler
}
func (o *GetResult) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewGetResultParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package results
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// NewGetResultParams creates a new GetResultParams object
//
// There are no default values defined in the spec.
func NewGetResultParams() GetResultParams {
return GetResultParams{}
}
// GetResultParams contains all the bound params for the get result operation
// typically these are obtained from a http.Request
//
// swagger:parameters getResult
type GetResultParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*SHA1 commit hash expressed in hexadecimal format
Pattern: ^[0-9a-fA-F]{40}$
In: query
*/
Commit *string
/*Name of the owner/organization of the repository
Required: true
In: path
*/
Org string
/*VCS platform. eg. github.com
Required: true
In: path
*/
Platform string
/*Name of the repository
Required: true
In: path
*/
Repo string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewGetResultParams() beforehand.
func (o *GetResultParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
qCommit, qhkCommit, _ := qs.GetOK("commit")
if err := o.bindCommit(qCommit, qhkCommit, route.Formats); err != nil {
res = append(res, err)
}
rOrg, rhkOrg, _ := route.Params.GetOK("org")
if err := o.bindOrg(rOrg, rhkOrg, route.Formats); err != nil {
res = append(res, err)
}
rPlatform, rhkPlatform, _ := route.Params.GetOK("platform")
if err := o.bindPlatform(rPlatform, rhkPlatform, route.Formats); err != nil {
res = append(res, err)
}
rRepo, rhkRepo, _ := route.Params.GetOK("repo")
if err := o.bindRepo(rRepo, rhkRepo, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindCommit binds and validates parameter Commit from query.
func (o *GetResultParams) bindCommit(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
o.Commit = &raw
if err := o.validateCommit(formats); err != nil {
return err
}
return nil
}
// validateCommit carries on validations for parameter Commit
func (o *GetResultParams) validateCommit(formats strfmt.Registry) error {
if err := validate.Pattern("commit", "query", *o.Commit, `^[0-9a-fA-F]{40}$`); err != nil {
return err
}
return nil
}
// bindOrg binds and validates parameter Org from path.
func (o *GetResultParams) bindOrg(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Org = raw
return nil
}
// bindPlatform binds and validates parameter Platform from path.
func (o *GetResultParams) bindPlatform(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Platform = raw
return nil
}
// bindRepo binds and validates parameter Repo from path.
func (o *GetResultParams) bindRepo(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Repo = raw
return nil
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package results
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/ossf/scorecard-webapp/app/generated/models"
)
// GetResultOKCode is the HTTP code returned for type GetResultOK
const GetResultOKCode int = 200
/*
GetResultOK A JSON object of the repository's ScorecardResult
swagger:response getResultOK
*/
type GetResultOK struct {
/*
In: Body
*/
Payload *models.ScorecardResult `json:"body,omitempty"`
}
// NewGetResultOK creates GetResultOK with default headers values
func NewGetResultOK() *GetResultOK {
return &GetResultOK{}
}
// WithPayload adds the payload to the get result o k response
func (o *GetResultOK) WithPayload(payload *models.ScorecardResult) *GetResultOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get result o k response
func (o *GetResultOK) SetPayload(payload *models.ScorecardResult) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetResultOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// GetResultBadRequestCode is the HTTP code returned for type GetResultBadRequest
const GetResultBadRequestCode int = 400
/*
GetResultBadRequest The request provided to the server was invalid
swagger:response getResultBadRequest
*/
type GetResultBadRequest struct {
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewGetResultBadRequest creates GetResultBadRequest with default headers values
func NewGetResultBadRequest() *GetResultBadRequest {
return &GetResultBadRequest{}
}
// WithPayload adds the payload to the get result bad request response
func (o *GetResultBadRequest) WithPayload(payload *models.Error) *GetResultBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get result bad request response
func (o *GetResultBadRequest) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetResultBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// GetResultNotFoundCode is the HTTP code returned for type GetResultNotFound
const GetResultNotFoundCode int = 404
/*
GetResultNotFound The content requested could not be found
swagger:response getResultNotFound
*/
type GetResultNotFound struct {
}
// NewGetResultNotFound creates GetResultNotFound with default headers values
func NewGetResultNotFound() *GetResultNotFound {
return &GetResultNotFound{}
}
// WriteResponse to the client
func (o *GetResultNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(404)
}
/*
GetResultDefault There was an internal error in the server while processing the request
swagger:response getResultDefault
*/
type GetResultDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewGetResultDefault creates GetResultDefault with default headers values
func NewGetResultDefault(code int) *GetResultDefault {
if code <= 0 {
code = 500
}
return &GetResultDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get result default response
func (o *GetResultDefault) WithStatusCode(code int) *GetResultDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get result default response
func (o *GetResultDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get result default response
func (o *GetResultDefault) WithPayload(payload *models.Error) *GetResultDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get result default response
func (o *GetResultDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetResultDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package results
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// GetResultURL generates an URL for the get result operation
type GetResultURL struct {
Org string
Platform string
Repo string
Commit *string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetResultURL) WithBasePath(bp string) *GetResultURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetResultURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *GetResultURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/projects/{platform}/{org}/{repo}"
org := o.Org
if org != "" {
_path = strings.Replace(_path, "{org}", org, -1)
} else {
return nil, errors.New("org is required on GetResultURL")
}
platform := o.Platform
if platform != "" {
_path = strings.Replace(_path, "{platform}", platform, -1)
} else {
return nil, errors.New("platform is required on GetResultURL")
}
repo := o.Repo
if repo != "" {
_path = strings.Replace(_path, "{repo}", repo, -1)
} else {
return nil, errors.New("repo is required on GetResultURL")
}
_basePath := o._basePath
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
var commitQ string
if o.Commit != nil {
commitQ = *o.Commit
}
if commitQ != "" {
qs.Set("commit", commitQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *GetResultURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *GetResultURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *GetResultURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on GetResultURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on GetResultURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *GetResultURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package results
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// PostResultHandlerFunc turns a function with the right signature into a post result handler
type PostResultHandlerFunc func(PostResultParams) middleware.Responder
// Handle executing the request and returning a response
func (fn PostResultHandlerFunc) Handle(params PostResultParams) middleware.Responder {
return fn(params)
}
// PostResultHandler interface for that can handle valid post result params
type PostResultHandler interface {
Handle(PostResultParams) middleware.Responder
}
// NewPostResult creates a new http.Handler for the post result operation
func NewPostResult(ctx *middleware.Context, handler PostResultHandler) *PostResult {
return &PostResult{Context: ctx, Handler: handler}
}
/*
PostResult swagger:route POST /projects/{platform}/{org}/{repo} results postResult
Publish a repository's OIDC verified ScorecardResult
*/
type PostResult struct {
Context *middleware.Context
Handler PostResultHandler
}
func (o *PostResult) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewPostResultParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package results
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"github.com/ossf/scorecard-webapp/app/generated/models"
)
// NewPostResultParams creates a new PostResultParams object
//
// There are no default values defined in the spec.
func NewPostResultParams() PostResultParams {
return PostResultParams{}
}
// PostResultParams contains all the bound params for the post result operation
// typically these are obtained from a http.Request
//
// swagger:parameters postResult
type PostResultParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Name of the owner/organization of the repository
Required: true
In: path
*/
Org string
/*VCS platform. eg. github.com
Required: true
In: path
*/
Platform string
/*
Required: true
In: body
*/
Publish *models.VerifiedScorecardResult
/*Name of the repository
Required: true
In: path
*/
Repo string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewPostResultParams() beforehand.
func (o *PostResultParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
rOrg, rhkOrg, _ := route.Params.GetOK("org")
if err := o.bindOrg(rOrg, rhkOrg, route.Formats); err != nil {
res = append(res, err)
}
rPlatform, rhkPlatform, _ := route.Params.GetOK("platform")
if err := o.bindPlatform(rPlatform, rhkPlatform, route.Formats); err != nil {
res = append(res, err)
}
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.VerifiedScorecardResult
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("publish", "body", ""))
} else {
res = append(res, errors.NewParseError("publish", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Publish = &body
}
}
} else {
res = append(res, errors.Required("publish", "body", ""))
}
rRepo, rhkRepo, _ := route.Params.GetOK("repo")
if err := o.bindRepo(rRepo, rhkRepo, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindOrg binds and validates parameter Org from path.
func (o *PostResultParams) bindOrg(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Org = raw
return nil
}
// bindPlatform binds and validates parameter Platform from path.
func (o *PostResultParams) bindPlatform(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Platform = raw
return nil
}
// bindRepo binds and validates parameter Repo from path.
func (o *PostResultParams) bindRepo(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Repo = raw
return nil
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package results
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/ossf/scorecard-webapp/app/generated/models"
)
// PostResultCreatedCode is the HTTP code returned for type PostResultCreated
const PostResultCreatedCode int = 201
/*
PostResultCreated Successfully updated ScorecardResult
swagger:response postResultCreated
*/
type PostResultCreated struct {
/*
In: Body
*/
Payload string `json:"body,omitempty"`
}
// NewPostResultCreated creates PostResultCreated with default headers values
func NewPostResultCreated() *PostResultCreated {
return &PostResultCreated{}
}
// WithPayload adds the payload to the post result created response
func (o *PostResultCreated) WithPayload(payload string) *PostResultCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the post result created response
func (o *PostResultCreated) SetPayload(payload string) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PostResultCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
// PostResultBadRequestCode is the HTTP code returned for type PostResultBadRequest
const PostResultBadRequestCode int = 400
/*
PostResultBadRequest The request provided to the server was invalid
swagger:response postResultBadRequest
*/
type PostResultBadRequest struct {
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewPostResultBadRequest creates PostResultBadRequest with default headers values
func NewPostResultBadRequest() *PostResultBadRequest {
return &PostResultBadRequest{}
}
// WithPayload adds the payload to the post result bad request response
func (o *PostResultBadRequest) WithPayload(payload *models.Error) *PostResultBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the post result bad request response
func (o *PostResultBadRequest) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PostResultBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*
PostResultDefault There was an internal error in the server while processing the request
swagger:response postResultDefault
*/
type PostResultDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewPostResultDefault creates PostResultDefault with default headers values
func NewPostResultDefault(code int) *PostResultDefault {
if code <= 0 {
code = 500
}
return &PostResultDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the post result default response
func (o *PostResultDefault) WithStatusCode(code int) *PostResultDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the post result default response
func (o *PostResultDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the post result default response
func (o *PostResultDefault) WithPayload(payload *models.Error) *PostResultDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the post result default response
func (o *PostResultDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PostResultDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// Code generated by go-swagger; DO NOT EDIT.
// Copyright 2021 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package results
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// PostResultURL generates an URL for the post result operation
type PostResultURL struct {
Org string
Platform string
Repo string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *PostResultURL) WithBasePath(bp string) *PostResultURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *PostResultURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *PostResultURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/projects/{platform}/{org}/{repo}"
org := o.Org
if org != "" {
_path = strings.Replace(_path, "{org}", org, -1)
} else {
return nil, errors.New("org is required on PostResultURL")
}
platform := o.Platform
if platform != "" {
_path = strings.Replace(_path, "{platform}", platform, -1)
} else {
return nil, errors.New("platform is required on PostResultURL")
}
repo := o.Repo
if repo != "" {
_path = strings.Replace(_path, "{repo}", repo, -1)
} else {
return nil, errors.New("repo is required on PostResultURL")
}
_basePath := o._basePath
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *PostResultURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *PostResultURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *PostResultURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on PostResultURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on PostResultURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *PostResultURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}
// Copyright 2022 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"net/http"
"net/url"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
_ "gocloud.dev/blob/gcsblob" // Needed to link in GCP drivers.
"github.com/ossf/scorecard-webapp/app/generated/models"
"github.com/ossf/scorecard-webapp/app/generated/restapi/operations/badge"
)
const (
shieldsURL = "https://img.shields.io/ossf-scorecard"
badgeLabel = "openssf+scorecard"
defaultStyle = "flat"
)
func GetBadgeHandler(params badge.GetBadgeParams) middleware.Responder {
host := params.Platform
orgName := params.Org
repoName := params.Repo
style := defaultStyle
if len(*params.Style) > 0 {
style = *params.Style
}
parsedURL, err := url.Parse(fmt.Sprintf("%s/%s/%s/%s?label=%s&style=%s", shieldsURL,
host,
orgName,
repoName,
badgeLabel,
style))
if err != nil {
return badge.NewGetBadgeDefault(http.StatusInternalServerError).WithPayload(&models.Error{
Code: http.StatusInternalServerError,
Message: err.Error(),
})
}
return middleware.ResponderFunc(func(rw http.ResponseWriter, producer runtime.Producer) {
http.Redirect(rw, params.HTTPRequest, parsedURL.String(), http.StatusFound)
})
}
// Copyright 2022 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"context"
"errors"
"log"
"net/http"
"path/filepath"
"strings"
"github.com/go-openapi/runtime/middleware"
"gocloud.dev/blob"
_ "gocloud.dev/blob/gcsblob" // Needed to link in GCP drivers.
"github.com/ossf/scorecard-webapp/app/generated/models"
"github.com/ossf/scorecard-webapp/app/generated/restapi/operations/results"
)
const (
scorecardResultBucketURL = "gs://ossf-scorecard-results"
scorecardCronResultBucketURL = "gs://ossf-scorecard-cron-results"
)
var errInvalidInputs = errors.New("invalid inputs provided")
func GetResultHandler(params results.GetResultParams) middleware.Responder {
res, err := getResults(params.Platform, params.Org, params.Repo, params.Commit)
if errors.Is(err, errNotFound) {
return results.NewGetResultNotFound()
}
if errors.Is(err, errInvalidInputs) {
return results.NewGetResultBadRequest()
}
if err == nil {
var ret models.ScorecardResult
if err = ret.UnmarshalBinary(res); err == nil {
return results.NewGetResultOK().WithPayload(&ret)
}
}
return results.NewGetResultDefault(http.StatusInternalServerError).WithPayload(&models.Error{
Code: http.StatusInternalServerError,
Message: err.Error(),
})
}
func getResults(host, orgName, repoName string, commit *string) ([]byte, error) {
// Sanitize input and log query.
cleanResultsFile, err := sanitizeInputs(host, orgName, repoName, commit)
if err != nil {
return nil, err
}
log.Printf("Querying GCS bucket for: %s", cleanResultsFile)
// Query GCS bucket.
ctx := context.Background()
if bucket, err := blob.OpenBucket(ctx, scorecardResultBucketURL); err == nil {
if results, err := bucket.ReadAll(ctx, cleanResultsFile); err == nil {
return results, nil
}
}
cleanResultsFile2, err := sanitizeInputs(host, orgName, repoName, commit)
if err != nil {
return nil, err
}
// Try the backup cron bucket.
if bucket, err := blob.OpenBucket(ctx, scorecardCronResultBucketURL); err == nil {
if results, err := bucket.ReadAll(ctx, cleanResultsFile2); err == nil {
return results, nil
}
}
return nil, errNotFound
}
func sanitizeInputs(host, orgName, repoName string, commit *string) (string, error) {
resultsFile := filepath.Join(host, orgName, repoName, "results.json")
if commit != nil {
resultsFile = filepath.Join(host, orgName, repoName, *commit, "results.json")
}
cleanResultsFile := filepath.Clean(resultsFile)
cleanResultsFile = strings.Replace(cleanResultsFile, "\n", "", -1)
cleanResultsFile = strings.Replace(cleanResultsFile, "\r", "", -1)
var matched bool
var err error
if commit == nil {
matched, err = filepath.Match("*/*/*/results.json", cleanResultsFile)
} else {
matched, err = filepath.Match("*/*/*/*/results.json", cleanResultsFile)
}
if err != nil || !matched {
return "", errInvalidInputs
}
return cleanResultsFile, nil
}
// Copyright 2022 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"net/http"
)
type githubTransport struct {
token string
}
func (transport githubTransport) RoundTrip(r *http.Request) (*http.Response, error) {
r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", transport.token))
return http.DefaultTransport.RoundTrip(r)
}
// Copyright 2024 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/google/go-github/v65/github"
"golang.org/x/mod/semver"
)
var errInvalidCodeQLVersion = errors.New("codeql version invalid")
type githubVerifier struct {
ctx context.Context
client *github.Client
cachedCommits map[commit]bool
codeqlActionMajor string
}
// returns a new githubVerifier, with an instantiated map.
// most uses should use this constructor.
func newGitHubVerifier(ctx context.Context, client *github.Client) *githubVerifier {
verifier := githubVerifier{
ctx: ctx,
client: client,
}
verifier.cachedCommits = map[commit]bool{}
return &verifier
}
// contains may make several "core" API calls:
// - one to get repository tags (we expect most cases to only need this call)
// - two to get the default branch name and check it
// - up to 10 requests when checking previous release branches
func (g *githubVerifier) contains(c commit) (bool, error) {
// return the cached answer if we've seen this commit before
if contains, ok := g.cachedCommits[c]; ok {
return contains, nil
}
// fetch 100 most recent tags first, as this should handle the most common scenario
if err := g.getTags(c.owner, c.repo); err != nil {
return false, err
}
// check cache again now that it's populated with tags
if contains, ok := g.cachedCommits[c]; ok {
return contains, nil
}
// check default branch
defaultBranch, err := g.defaultBranch(c.owner, c.repo)
if err != nil {
return false, err
}
contains, err := g.branchContains(defaultBranch, c.owner, c.repo, c.hash)
if err != nil {
return false, err
}
if contains {
return true, nil
}
// finally, check the most recent 10 release branches. This limit is arbitrary and can be adjusted in the future.
const lookback = 10
return g.checkReleaseBranches(c.owner, c.repo, c.hash, defaultBranch, lookback)
}
func (g *githubVerifier) defaultBranch(owner, repo string) (string, error) {
githubRepository, _, err := g.client.Repositories.Get(g.ctx, owner, repo)
if err != nil {
return "", fmt.Errorf("fetching repository info: %w", err)
}
if githubRepository == nil || githubRepository.DefaultBranch == nil {
return "", errNoDefaultBranch
}
return *githubRepository.DefaultBranch, nil
}
func (g *githubVerifier) branchContains(branch, owner, repo, hash string) (bool, error) {
opts := &github.ListOptions{PerPage: 1}
diff, resp, err := g.client.Repositories.CompareCommits(g.ctx, owner, repo, branch, hash, opts)
if err != nil {
if resp.StatusCode == http.StatusNotFound {
// NotFound can be returned for some divergent cases: "404 No common ancestor between ..."
return false, nil
}
return false, fmt.Errorf("error comparing revisions: %w", err)
}
// Target should be behind or at the base ref if it is considered contained.
contains := diff.GetStatus() == "behind" || diff.GetStatus() == "identical"
if contains {
g.markContains(owner, repo, hash)
}
return contains, nil
}
func (g *githubVerifier) getTags(owner, repo string) error {
// 100 releases is the maximum supported by /repos/{owner}/{repo}/tags endpoint
opts := github.ListOptions{PerPage: 100}
tags, _, err := g.client.Repositories.ListTags(g.ctx, owner, repo, &opts)
if err != nil {
return fmt.Errorf("fetch tags: %w", err)
}
isCodeQL := owner == "github" && repo == "codeql-action"
for _, t := range tags {
if t.Commit != nil && t.Commit.SHA != nil {
g.markContains(owner, repo, *t.Commit.SHA)
}
// store the highest major version for github/codeql-action
// this helps check release branches later, due to their release process.
if isCodeQL {
version := t.GetName()
if semver.IsValid(version) && semver.Compare(version, g.codeqlActionMajor) == 1 {
g.codeqlActionMajor = semver.Major(version)
}
}
}
return nil
}
func (g *githubVerifier) markContains(owner, repo, sha string) {
commit := commit{
owner: owner,
repo: repo,
hash: strings.ToLower(sha),
}
g.cachedCommits[commit] = true
}
// check the most recent release branches, ignoring the default branch which was already checked.
func (g *githubVerifier) checkReleaseBranches(owner, repo, hash, defaultBranch string, limit int) (bool, error) {
var (
analyzedBranches int
branches []string
err error
)
switch {
// special case: github/codeql-action releases all come from "main", even though the tags are on different branches
case owner == "github" && repo == "codeql-action":
branches, err = g.getCodeQLReleaseBranches()
if err != nil {
return false, err
}
default:
branches, err = g.getReleaseBranches(owner, repo)
if err != nil {
return false, err
}
// we may have discovered more commit SHAs from the release process
c := commit{
owner: owner,
repo: repo,
hash: hash,
}
if contains, ok := g.cachedCommits[c]; ok {
return contains, nil
}
}
for _, branch := range branches {
if analyzedBranches >= limit {
break
}
if branch == defaultBranch {
continue
}
analyzedBranches++
contains, err := g.branchContains(branch, owner, repo, hash)
if err != nil {
return false, err
}
if contains {
return true, nil
}
}
return false, nil
}
// returns the integer version from the expected format: "v1", "v2", "v3" ..
func parseCodeQLVersion(version string) (int, error) {
if !strings.HasPrefix(version, "v") {
return 0, fmt.Errorf("%w: %s", errInvalidCodeQLVersion, version)
}
major, err := strconv.Atoi(version[1:])
if major < 1 || err != nil {
return 0, fmt.Errorf("%w: %s", errInvalidCodeQLVersion, version)
}
return major, nil
}
// these branches follow the releases/v3 pattern, so we can make assumptions about what they're called.
// this should be called after g.getTags(), because it requires g.codeqlActionMajor to be set.
func (g *githubVerifier) getCodeQLReleaseBranches() ([]string, error) {
if g.codeqlActionMajor == "" {
return nil, nil
}
version, err := parseCodeQLVersion(g.codeqlActionMajor)
if err != nil {
return nil, err
}
branches := make([]string, version)
// descending order (e..g releases/v5, releases/v4, ... releases/v1)
for i := 0; i < version; i++ {
branches[i] = "releases/v" + strconv.Itoa(version-i)
}
return branches, nil
}
// fetch the last 100 releases and return any branches referenced by a release
//
// note: releases may refer to commits, so for API efficiency, this method also
// does some bookkeeping for any commits encountered.
func (g *githubVerifier) getReleaseBranches(owner, repo string) ([]string, error) {
var branches []string
seen := map[string]struct{}{}
// 100 releases is the maximum supported by /repos/{owner}/{repo}/releases endpoint
opts := github.ListOptions{PerPage: 100}
releases, _, err := g.client.Repositories.ListReleases(g.ctx, owner, repo, &opts)
if err != nil {
return nil, fmt.Errorf("fetch releases: %w", err)
}
for _, r := range releases {
if r.TargetCommitish != nil {
if isCommitHash(*r.TargetCommitish) {
// if a commit, we know it's in the repo
g.markContains(owner, repo, *r.TargetCommitish)
} else {
// otherwise we have a release branch to check
if _, ok := seen[*r.TargetCommitish]; !ok {
seen[*r.TargetCommitish] = struct{}{}
branches = append(branches, *r.TargetCommitish)
}
}
}
}
return branches, nil
}
// Copyright 2023 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hashedrekord
import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"log"
)
// https://github.com/sigstore/rekor/blob/f01f9cd2c55eaddba9be28624fea793a26ad28c4/pkg/types/README.md
const Kind = "hashedrekord"
// https://github.com/sigstore/rekor/blob/f01f9cd2c55eaddba9be28624fea793a26ad28c4/pkg/types/hashedrekord/v0.0.1/hashedrekord_v0_0_1_schema.json
//
//nolint:lll
type Body struct {
APIVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Spec Spec `json:"spec"`
}
type Spec struct {
Data Data `json:"data"`
Signature Signature `json:"signature"`
}
type Data struct {
Hash Hash `json:"hash"`
}
type Hash struct {
Algorithm string `json:"algorithm"`
Value string `json:"value"`
}
type Signature struct {
Content string `json:"content"`
PublicKey PublicKey `json:"publicKey"`
}
type PublicKey struct {
Content string `json:"content"`
}
// check if the rekord object matches a given blob (currently compares sha256 hash).
func (b Body) Matches(blob []byte) bool {
if b.Spec.Data.Hash.Algorithm != "sha256" {
log.Println("hashedrekord entry has no sha256")
return false
}
sha := sha256.Sum256(blob)
have := hex.EncodeToString(sha[:])
want := b.Spec.Data.Hash.Value
return have == want
}
// extracts all x509 certs from the hashedrekord tlog entry public key.
func (b Body) Certs() ([]*x509.Certificate, error) {
publicKey, err := base64.StdEncoding.DecodeString(b.Spec.Signature.PublicKey.Content)
if err != nil {
return nil, fmt.Errorf("decode rekord public key: %w", err)
}
remaining := publicKey
var result []*x509.Certificate
for len(remaining) > 0 {
var certDer *pem.Block
certDer, remaining = pem.Decode(remaining)
if certDer == nil {
return nil, fmt.Errorf("error during PEM decoding: %w", err)
}
cert, err := x509.ParseCertificate(certDer.Bytes)
if err != nil {
return nil, fmt.Errorf("error during certificate parsing: %w", err)
}
result = append(result, cert)
}
return result, nil
}
// Copyright 2022 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/sha256"
"crypto/x509"
_ "embed"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/google/go-github/v65/github"
merkleproof "github.com/transparency-dev/merkle/proof"
"github.com/transparency-dev/merkle/rfc6962"
"gocloud.dev/blob"
"github.com/ossf/scorecard-webapp/app/generated/models"
"github.com/ossf/scorecard-webapp/app/generated/restapi/operations/results"
"github.com/ossf/scorecard-webapp/app/server/internal/hashedrekord"
)
const (
// OID source: https://github.com/sigstore/fulcio/blob/96ef49cc7662912ba37d46f738757e8d8d5b5355/docs/oid-info.md#L33
// TODO: retrieve these by name.
fulcioRepoRefKey = "1.3.6.1.4.1.57264.1.6"
fulcioRepoPathKey = "1.3.6.1.4.1.57264.1.5"
fulcioRepoSHAKey = "1.3.6.1.4.1.57264.1.3"
fulcioIssuerKey = "1.3.6.1.4.1.57264.1.1"
resultsBucket = "gs://ossf-scorecard-results"
resultsFile = "results.json"
noTlogIndex = 0
githubOIDCIssuer = "https://token.actions.githubusercontent.com"
)
var (
errWritingBucket = errors.New("error writing to GCS bucket")
errMultipleCerts = errors.New("multiple certificates found for the entry")
errEmptyCertRef = errors.New("cert has empty repository ref")
errEmptyCertPath = errors.New("cert has empty repository path")
errCertMissingURI = errors.New("certificate has no URIs")
errCertWorkflowPathEmpty = errors.New("cert workflow path is empty")
errMismatchedCertAndRequest = errors.New("repository and branch of cert doesn't match that of request")
errNotDefaultBranch = errors.New("branch of cert isn't the repo's default branch")
errNoTlogEntry = errors.New("no transparency log entry found")
errNotRekordEntry = errors.New("not a rekord entry")
errMismatchedTlogEntry = errors.New("tlog entry does not match payload")
errNotOIDC = errors.New(`ensure your GitHub workflow has "id-token: write" permissions`)
)
type certInfo struct {
repoFullName string
repoBranchRef string
repoSHA string
workflowPath string
workflowRef string
issuer string
}
type tlogEntry struct {
Body string `json:"body"`
IntegratedTime int64 `json:"integratedTime"`
LogID string `json:"logID"`
LogIndex int64 `json:"logIndex"`
Verification *struct {
InclusionProof *struct {
Hashes []string `json:"hashes"`
RootHash string `json:"rootHash"`
TreeSize uint64 `json:"treeSize"`
LogIndex uint64 `json:"logIndex"`
} `json:"inclusionProof,omitempty"`
SignedEntryTimestamp strfmt.Base64 `json:"signedEntryTimestamp,omitempty"`
} `json:"verification"`
}
//go:embed fulcio_v1.crt.pem
var fulcioRoot []byte
//go:embed fulcio_intermediate.crt.pem
var fulcioIntermediate []byte
//go:embed rekor.pub
var rekorPub []byte
func PostResultsHandler(params results.PostResultParams) middleware.Responder {
// Sanity check
host := params.Platform
orgName := params.Org
repoName := params.Repo
// Process
err := processRequest(host, orgName, repoName, params.Publish)
if err == nil {
return results.NewPostResultCreated().WithPayload("successfully verified and published ScorecardResult")
}
var vErr verificationError
if errors.As(err, &vErr) || errors.Is(err, errWorkflowParse) || errors.Is(err, errNotOIDC) {
return results.NewPostResultBadRequest().WithPayload(&models.Error{
Code: http.StatusBadRequest,
Message: err.Error(),
})
}
log.Println(err)
return results.NewPostResultDefault(http.StatusInternalServerError).WithPayload(&models.Error{
Code: http.StatusInternalServerError,
Message: "something went wrong and we are looking into it.",
})
}
func processRequest(host, org, repo string, scorecardResult *models.VerifiedScorecardResult) error {
ctx := context.Background()
cert, err := extractAndVerifyCertForPayload(ctx, []byte(scorecardResult.Result), scorecardResult.TlogIndex)
if err != nil {
return fmt.Errorf("error extracting cert: %w", err)
}
info, err := extractCertInfo(cert)
if err != nil {
return fmt.Errorf("error extracting cert info: %w", err)
}
if info.repoFullName != fullName(org, repo) ||
(info.repoBranchRef != scorecardResult.Branch &&
info.repoBranchRef != fmt.Sprintf("refs/heads/%s", scorecardResult.Branch)) {
return verificationError{e: errMismatchedCertAndRequest}
}
if err := getAndVerifyWorkflowContent(ctx, scorecardResult, info); err != nil {
return fmt.Errorf("workflow verification failed: %w", err)
}
// Save scorecard results (results.json, score.txt) to GCS
bucketURL := resultsBucket
objectPath := fmt.Sprintf("%s/%s/%s/%s", host, org, repo, resultsFile)
if err := writeToBlobStore(ctx, bucketURL, objectPath, []byte(scorecardResult.Result)); err != nil {
return fmt.Errorf("%w: %v", errWritingBucket, err)
}
commitObjectPath := fmt.Sprintf("%s/%s/%s/%s/%s", host, org, repo, info.repoSHA, resultsFile)
if err := writeToBlobStore(ctx, bucketURL, commitObjectPath, []byte(scorecardResult.Result)); err != nil {
return fmt.Errorf("%w: %v", errWritingBucket, err)
}
return nil
}
func fullName(org, repo string) string {
return fmt.Sprintf("%s/%s", org, repo)
}
// splitFullPath extracts the org, repo, and path from a full path of the form org/repo/rest/of/path.
func splitFullPath(path string) (org, repo, subPath string, ok bool) {
parts := strings.SplitN(path, "/", 3)
if len(parts) < 3 {
return "", "", "", false
}
return parts[0], parts[1], parts[2], true
}
// splitRepoName extracts the org, repo from a full repository name.
func splitRepoName(path string) (org, repo string, ok bool) {
parts := strings.SplitN(path, "/", 2)
if len(parts) < 2 {
return "", "", false
}
return parts[0], parts[1], true
}
// getAndVerifyWorkflowContent retrieves the workflow content from the repository and verifies it.
// It verifies the branch is a default branch and gets the scorecard workflow from the repository
// from the specific commit and verifies it to ensure that it hasn't been tampered with.
func getAndVerifyWorkflowContent(ctx context.Context,
scorecardResult *models.VerifiedScorecardResult, info certInfo,
) error {
// Get the corresponding GitHub repository.
httpClient := http.DefaultClient
if scorecardResult.AccessToken != "" {
httpClient.Transport = githubTransport{
token: scorecardResult.AccessToken,
}
}
client := github.NewClient(httpClient)
// Organization and repo of the project being analyzed
org, repo, ok := splitRepoName(info.repoFullName)
if !ok {
return fmt.Errorf("cert repository name is malformed")
}
repoClient, _, err := client.Repositories.Get(ctx, org, repo)
if err != nil {
return fmt.Errorf("error getting repository: %w", err)
}
// Verify that the branch from the results files is the repo's default branch.
defaultBranch := repoClient.GetDefaultBranch()
if scorecardResult.Branch != defaultBranch &&
scorecardResult.Branch != fmt.Sprintf("refs/heads/%s", defaultBranch) {
return verificationError{e: errNotDefaultBranch}
}
// Organization and repo of the (possibly) reusable workflow
workflowOrg, workflowRepo, path, ok := splitFullPath(info.workflowPath)
if !ok {
return fmt.Errorf("cert workflow path is malformed")
}
workflowRepoFullName := fullName(workflowOrg, workflowRepo)
// Use the cert commit SHA if the workflow file is in the repo being analyzed.
// Otherwise fall back to the workflowRef, which may be a commit SHA, or it may be more vague e.g. refs/heads/main
opts := &github.RepositoryContentGetOptions{Ref: info.repoSHA}
if workflowRepoFullName != info.repoFullName {
opts.Ref = info.workflowRef
}
contents, _, _, err := client.Repositories.GetContents(ctx, workflowOrg, workflowRepo, path, opts)
if err != nil {
return fmt.Errorf("error downloading workflow contents from repo: %w", err)
}
workflowContent, err := contents.GetContent()
if err != nil {
return fmt.Errorf("error decoding workflow contents: %w", err)
}
verifier := newGitHubVerifier(ctx, client)
// Verify scorecard workflow.
return verifyScorecardWorkflow(workflowContent, verifier)
}
func writeToBlobStore(ctx context.Context, bucketURL, filename string, data []byte) error {
bucket, err := blob.OpenBucket(ctx, bucketURL)
if err != nil {
return fmt.Errorf("error from blob.OpenBucket: %w", err)
}
defer bucket.Close()
blobWriter, err := bucket.NewWriter(ctx, filename, nil)
if err != nil {
return fmt.Errorf("error from bucket.NewWriter: %w", err)
}
if _, err = blobWriter.Write(data); err != nil {
return fmt.Errorf("error from blobWriter.Write: %w", err)
}
if err := blobWriter.Close(); err != nil {
return fmt.Errorf("error from blobWriter.Close: %w", err)
}
return nil
}
func extractAndVerifyCertForPayload(ctx context.Context, payload []byte, tlogIndex int64) (*x509.Certificate, error) {
var entry *tlogEntry
var uuid string
var err error
// #135 older versions of scorecard action wont send the tlog index, but newer ones will
if tlogIndex == noTlogIndex {
// Get most recent Rekor entry uuid.
uuids, err := getUUIDsByPayload(ctx, payload)
if err != nil || len(uuids) == 0 {
return nil, fmt.Errorf("error finding tlog entries corresponding to payload: %w", err)
}
uuid = uuids[len(uuids)-1] // ignore past entries.
// Get tlog entry from the UUID.
entry, err = getTLogEntryByUUID(ctx, uuid)
if err != nil {
return nil, fmt.Errorf("error fetching tlog entry: %w", err)
}
} else {
uuid, entry, err = getTLogEntryByIndex(ctx, tlogIndex)
if err != nil {
return nil, fmt.Errorf("error fetching tlog entry: %w", err)
}
}
rekordBody, err := entry.rekord()
if err != nil {
return nil, err
}
if !rekordBody.Matches(payload) {
return nil, errMismatchedTlogEntry
}
// Verify inclusion proof.
if err = verifyInclusionProof(uuid, entry); err != nil {
return nil, fmt.Errorf("unable to verify rekor inclusion proof: %w", err)
}
// Extract and verify certificate.
certs, err := rekordBody.Certs()
if err != nil || len(certs) == 0 {
return nil, fmt.Errorf("error extracting certificate from entry: %w", err)
}
if len(certs) > 1 {
return nil, errMultipleCerts
}
cert := certs[0]
if err = verifyCert(certs[0], time.Unix(entry.IntegratedTime, 0)); err != nil {
return nil, fmt.Errorf("verifying cert: %w", err)
}
return cert, nil
}
// getUUIDsByPayload returns the UUIDs of the Rekor entries that contain the given payload.
// It takes the payload as a byte array and converts it to a SHA256 hash.
// It then queries the Rekor server for all entries that contain the hash.
// It returns the UUIDs of the entries that contain the payload.
func getUUIDsByPayload(ctx context.Context, payload []byte) ([]string, error) {
payloadSHA := sha256.Sum256(payload)
rekorPayload := struct {
Hash string `json:"hash"`
}{
Hash: fmt.Sprintf("sha256:%s", hex.EncodeToString(payloadSHA[:])),
}
jsonPayload, err := json.Marshal(rekorPayload)
if err != nil {
return nil, fmt.Errorf("marshaling json payload: %w", err)
}
rekorReq, err := http.NewRequestWithContext(ctx,
http.MethodPost,
"https://rekor.sigstore.dev/api/v1/index/retrieve",
bytes.NewBuffer(jsonPayload))
if err != nil {
return nil, fmt.Errorf("creating new HTTP request: %w", err)
}
rekorReq.Header.Add("Content-Type", "application/json")
rekorReq.Header.Add("accept", "application/json")
resp, err := http.DefaultClient.Do(rekorReq)
if err != nil {
return nil, fmt.Errorf("looking up Rekor index: %w", err)
}
defer resp.Body.Close()
var rekorResult []string
if err := json.NewDecoder(resp.Body).Decode(&rekorResult); err != nil {
return nil, fmt.Errorf("decoding Rekor response: %w", err)
}
return rekorResult, nil
}
// tlog entry helper function, url should be a variation of the https://rekor.sigstore.dev/api/v1/log/entries endpoint.
func getTLogEntryFromURL(ctx context.Context, url string) (uuid string, entry *tlogEntry, err error) {
rekorReq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", nil, fmt.Errorf("creating new HTTP request: %w", err)
}
rekorReq.Header.Add("accept", "application/json")
resp, err := http.DefaultClient.Do(rekorReq)
if err != nil {
return "", nil, fmt.Errorf("looking up Rekor index: %w", err)
}
defer resp.Body.Close()
var rekorResult map[string]tlogEntry
if err := json.NewDecoder(resp.Body).Decode(&rekorResult); err != nil {
return "", nil, fmt.Errorf("decoding Rekor response: %w", err)
}
for uuid, res := range rekorResult {
return uuid, &res, nil
}
return "", nil, errNoTlogEntry
}
// getTLogEntryByIndex fetches the UUID and tlog entry from Rekor by tlog index.
func getTLogEntryByIndex(ctx context.Context, index int64) (uuid string, entry *tlogEntry, err error) {
url := fmt.Sprintf("https://rekor.sigstore.dev/api/v1/log/entries?logIndex=%d", index)
return getTLogEntryFromURL(ctx, url)
}
// getTLogEntryByUUID fetches the tlog entry from Rekor by UUID.
func getTLogEntryByUUID(ctx context.Context, uuid string) (*tlogEntry, error) {
url := fmt.Sprintf("https://rekor.sigstore.dev/api/v1/log/entries/%s", uuid)
_, entry, err := getTLogEntryFromURL(ctx, url)
return entry, err
}
// verifyInclusionProof verifies the inclusion proof of the tlog entry.
// It hex decodes the RootHash from the tlog entry and hex decodes the uuid as the leaf hash.
// It then verifies the merkelproof using the RootHash, LeafHash, and InclusionProof hashes from the
// tlog entry. It also ensures that the timestamp of the tlog entry was signed by rekor public key.
func verifyInclusionProof(uuid string, e *tlogEntry) error {
if e == nil || e.Verification == nil || e.Verification.InclusionProof == nil {
return fmt.Errorf("no inclusion proof provided")
}
rootHash, err := hex.DecodeString(e.Verification.InclusionProof.RootHash)
if err != nil {
return fmt.Errorf("error decoding hex encoded root hash: %w", err)
}
leafHash, err := hex.DecodeString(uuid)
if err != nil {
return fmt.Errorf("error decoding hex encoded leaf hash: %w", err)
}
if len(leafHash) < 32 {
return fmt.Errorf("leafHash has unexpected size %d, want 32", len(leafHash))
}
if len(leafHash) > 32 {
leafHash = leafHash[len(leafHash)-32:]
}
var hashes [][]byte
for _, h := range e.Verification.InclusionProof.Hashes {
hb, err := hex.DecodeString(h)
if err != nil {
return fmt.Errorf("error decoding inclusion proof hashes: %w", err)
}
hashes = append(hashes, hb)
}
if err := merkleproof.VerifyInclusion(rfc6962.DefaultHasher,
e.Verification.InclusionProof.LogIndex,
e.Verification.InclusionProof.TreeSize, leafHash, hashes, rootHash); err != nil {
return fmt.Errorf("%w: %s", err, "verifying inclusion proof")
}
// Verify the SignedEntryTimestamp against Rekor's pub key.
derBytes, _ := pem.Decode(rekorPub)
if derBytes == nil {
return errors.New("PEM decoding failed")
}
rekorPubKey, err := x509.ParsePKIXPublicKey(derBytes.Bytes)
if err != nil {
return fmt.Errorf("parsing Rekor pub key: %w", err)
}
rekorECDSA, ok := rekorPubKey.(*ecdsa.PublicKey)
if !ok {
return errors.New("public key retrieved from Rekor is not an ECDSA key")
}
payload := struct {
Body string `json:"body"`
IntegratedTime int64 `json:"integratedTime"`
LogID string `json:"logID"`
LogIndex int64 `json:"logIndex"`
}{
Body: e.Body,
IntegratedTime: e.IntegratedTime,
LogID: e.LogID,
LogIndex: e.LogIndex,
}
jsonPayload, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("json marshalling Rekor payload: %w", err)
}
jsonCanonicalized, err := jsoncanonicalizer.Transform(jsonPayload)
if err != nil {
return fmt.Errorf("json canonicalizer: %w", err)
}
hash := sha256.Sum256(jsonCanonicalized)
if !ecdsa.VerifyASN1(rekorECDSA, hash[:], e.Verification.SignedEntryTimestamp) {
return fmt.Errorf("unable to verify")
}
return nil
}
// verifyCert verifies the certificate from the tlog entry against the fulcio root cert and
// fulcio intermediate cert.
// It also verifies the certs are not expired by checking the notBefore and notAfter fields based
// on the integratedTime from the tlog entry.
func verifyCert(cert *x509.Certificate, integratedTime time.Time) error {
// Verify the certificate against Fulcio Root CA
roots, err := getCertPool(fulcioRoot)
if err != nil {
return fmt.Errorf("retrieving Fulcio root: %w", err)
}
intermediates, err := getCertPool(fulcioIntermediate)
if err != nil {
return fmt.Errorf("retrieving Fulcio root: %w", err)
}
if _, err := cert.Verify(x509.VerifyOptions{
CurrentTime: cert.NotBefore,
Roots: roots,
Intermediates: intermediates,
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageCodeSigning,
},
}); err != nil {
return fmt.Errorf("verifying Fulcio issued certificate: %w", err)
}
// Verify that cert isn't expired.
if cert.NotAfter.Before(integratedTime) {
return fmt.Errorf("certificate expired before signatures were entered in log: %s is before %s",
cert.NotAfter, integratedTime)
}
if cert.NotBefore.After(integratedTime) {
return fmt.Errorf("certificate was issued after signatures were entered in log: %s is after %s",
cert.NotAfter, integratedTime)
}
return nil
}
// extractCertInfo extracts the repository information from the certificate.
// These certificates are issued by Fulcio and have extensions with the repository information.
// These extensions are extracted and returned as certInfo.
func extractCertInfo(cert *x509.Certificate) (certInfo, error) {
ret := certInfo{}
// Get repo reference & path from cert.
for _, ext := range cert.Extensions {
if ext.Id.String() == fulcioRepoRefKey {
if len(ext.Value) == 0 {
return ret, errEmptyCertRef
}
ret.repoBranchRef = string(ext.Value)
}
if ext.Id.String() == fulcioRepoPathKey {
if len(ext.Value) == 0 {
return ret, errEmptyCertPath
}
ret.repoFullName = string(ext.Value)
}
if ext.Id.String() == fulcioRepoSHAKey {
ret.repoSHA = string(ext.Value)
}
if ext.Id.String() == fulcioIssuerKey {
ret.issuer = string(ext.Value)
}
}
// if this is something else, like https://github.com/login/oauth then cosign couldnt get an ambient token
if ret.issuer != githubOIDCIssuer {
return ret, errNotOIDC
}
// Get workflow job ref from the certificate.
if len(cert.URIs) == 0 {
return ret, errCertMissingURI
}
workflowRef := cert.URIs[0].Path
if len(workflowRef) == 0 {
return ret, errCertWorkflowPathEmpty
}
// url.URL.Path may have leading slashes
ret.workflowPath = strings.TrimLeft(workflowRef, "/")
// Remove repo ref tag from workflow filepath.
ret.workflowPath, ret.workflowRef, _ = strings.Cut(ret.workflowPath, "@")
return ret, nil
}
func getCertPool(cert []byte) (*x509.CertPool, error) {
pool := x509.NewCertPool()
if ok := pool.AppendCertsFromPEM(cert); !ok {
return nil, fmt.Errorf("unmarshalling PEM certificate")
}
return pool, nil
}
func (t tlogEntry) rekord() (hashedrekord.Body, error) {
b, err := base64.StdEncoding.DecodeString(t.Body)
if err != nil {
return hashedrekord.Body{}, fmt.Errorf("decode rekord body: %w", err)
}
var body hashedrekord.Body
if err := json.Unmarshal(b, &body); err != nil {
return hashedrekord.Body{}, fmt.Errorf("unmarshal rekord body: %w", err)
}
if body.Kind != hashedrekord.Kind {
return hashedrekord.Body{}, errNotRekordEntry
}
return body, nil
}
// Copyright 2022 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"errors"
"fmt"
"regexp"
"strings"
"github.com/rhysd/actionlint"
)
const (
workflowRestrictionLink = "https://github.com/ossf/scorecard-action#workflow-restrictions"
)
var (
errWorkflowParse = errors.New("unable to parse github workflow")
errGlobalVarsOrDefaults = errors.New("workflow contains global env vars or defaults")
errGlobalWriteAll = errors.New("global perm is set to write-all")
errGlobalWrite = errors.New("global perm is set to write")
errScorecardJobNotFound = errors.New("workflow has no job that calls ossf/scorecard-action")
errNonScorecardJobHasTokenWrite = errors.New("workflow has a non-scorecard job with id-token permissions")
errJobHasContainerOrServices = errors.New("job contains container or service")
errScorecardJobRunsOn = errors.New("scorecard job should have exactly 1 'Ubuntu' virtual environment")
errInvalidRunnerLabel = errors.New("scorecard job has invalid runner label")
errUnallowedStepName = errors.New("job has unallowed step")
errScorecardJobEnvVars = errors.New("scorecard job contains env vars")
errScorecardJobDefaults = errors.New("scorecard job must not have defaults set")
errEmptyStepUses = errors.New("scorecard job must only have steps with `uses`")
errNoDefaultBranch = errors.New("no default branch")
reCommitSHA = regexp.MustCompile(`^[0-9a-fA-F]{40}$`)
)
// TODO(#290): retrieve the runners dynamically.
// List below is from https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners.
var ubuntuRunners = map[string]bool{
"ubuntu-latest": true,
"ubuntu-24.04": true,
"ubuntu-22.04": true,
"ubuntu-20.04": true,
"ubuntu-18.04": true,
}
type commit struct {
owner, repo, hash string
}
type commitVerifier interface {
contains(c commit) (bool, error)
}
type verificationError struct {
e error
}
func (ve verificationError) Error() string {
return fmt.Sprintf("workflow verification failed: %v, see %s for details.", ve.e, workflowRestrictionLink)
}
func (ve verificationError) Unwrap() error {
return ve.e
}
type imposterCommitError struct {
action, ref string
}
func (i imposterCommitError) Error() string {
return fmt.Sprintf("imposter commit: %s does not belong to %s", i.ref, i.action)
}
func verifyScorecardWorkflow(workflowContent string, verifier commitVerifier) error {
// Verify workflow contents using actionlint.
workflow, lintErrs := actionlint.Parse([]byte(workflowContent))
if lintErrs != nil || workflow == nil {
return fmt.Errorf("%w: %v", errWorkflowParse, lintErrs)
}
// Verify that there are no global env vars or defaults.
if workflow.Env != nil || workflow.Defaults != nil {
return verificationError{e: errGlobalVarsOrDefaults}
}
if workflow.Permissions != nil {
globalPerms := workflow.Permissions
// Verify that the all scope, if set, isn't write-all.
if globalPerms.All != nil && globalPerms.All.Value == "write-all" {
return verificationError{e: errGlobalWriteAll}
}
// Verify that there are no global permissions (including id-token) set to write.
for globalPerm, val := range globalPerms.Scopes {
if val.Value.Value == "write" {
return verificationError{e: fmt.Errorf("%w: permission for %v is set to write",
errGlobalWrite, globalPerm)}
}
}
}
// Find the (first) job with a step that calls scorecard-action.
scorecardJob := findScorecardJob(workflow.Jobs)
if scorecardJob == nil {
return verificationError{e: errScorecardJobNotFound}
}
// Make sure other jobs don't have id-token permissions.
for _, job := range workflow.Jobs {
if job != scorecardJob && job.Permissions != nil {
idToken := job.Permissions.Scopes["id-token"]
if idToken != nil && idToken.Value.Value == "write" {
return verificationError{e: errNonScorecardJobHasTokenWrite}
}
}
}
// Verify that there is no job container or services.
if scorecardJob.Container != nil || hasServices(scorecardJob) {
return verificationError{e: errJobHasContainerOrServices}
}
labels := scorecardJob.RunsOn.Labels
if len(labels) != 1 {
return verificationError{e: errScorecardJobRunsOn}
}
label := labels[0].Value
if _, ok := ubuntuRunners[label]; !ok {
return fmt.Errorf("%w: '%s'", errInvalidRunnerLabel, label)
}
// Verify that there are no job env vars set.
if scorecardJob.Env != nil {
return verificationError{e: errScorecardJobEnvVars}
}
// Verify that there are no job defaults set.
if scorecardJob.Defaults != nil {
return verificationError{e: errScorecardJobDefaults}
}
// Get steps in job.
steps := scorecardJob.Steps
// Verify that steps are valid (checkout, scorecard-action, upload-artifact, upload-sarif).
for _, step := range steps {
stepUses := getStepUses(step)
if stepUses == nil {
return verificationError{e: errEmptyStepUses}
}
stepName, ref := parseStep(stepUses.Value)
switch stepName {
case
"actions/checkout",
"actions/create-github-app-token",
"ossf/scorecard-action",
"actions/upload-artifact",
"github/codeql-action/upload-sarif",
"step-security/harden-runner":
if isCommitHash(ref) {
s := strings.Split(stepName, "/")
// no need to length check as the step name is one of the ones above
c := commit{
owner: s[0],
repo: s[1],
hash: ref,
}
contains, err := verifier.contains(c)
if err != nil {
return err
}
if !contains {
return verificationError{e: imposterCommitError{ref: ref, action: stepName}}
}
}
// Needed for e2e tests
case "gcr.io/openssf/scorecard-action", "ghcr.io/ossf/scorecard-action":
default:
return verificationError{e: fmt.Errorf("%w: %s", errUnallowedStepName, stepName)}
}
}
return nil
}
// Finds the job with a step that calls ossf/scorecard-action.
func findScorecardJob(jobs map[string]*actionlint.Job) *actionlint.Job {
for _, job := range jobs {
if job == nil {
continue
}
for _, step := range job.Steps {
stepUses := getStepUses(step)
if stepUses == nil {
continue
}
stepName, _ := parseStep(stepUses.Value)
if stepName == "ossf/scorecard-action" ||
stepName == "gcr.io/openssf/scorecard-action" ||
stepName == "ghcr.io/ossf/scorecard-action" {
return job
}
}
}
return nil
}
func parseStep(step string) (name, ref string) {
// Check for `uses: ossf/scorecard-action@ref`.
reRef := regexp.MustCompile(`^([^@]*)@(.*)$`)
refMatches := reRef.FindStringSubmatch(step)
if len(refMatches) > 2 {
return refMatches[1], refMatches[2]
}
// Check for `uses: docker://gcr.io/openssf/scorecard-action:tag`.
reDocker := regexp.MustCompile(`^docker://([^:]*):.*$`)
dockerMatches := reDocker.FindStringSubmatch(step)
if len(dockerMatches) > 1 {
// TODO don't currently need ref for the docker images
return dockerMatches[1], ""
}
return "", ""
}
func getStepUses(step *actionlint.Step) *actionlint.String {
if step.Exec == nil {
return nil
}
execAction, exists := step.Exec.(*actionlint.ExecAction)
if !exists || execAction == nil {
return nil
}
return execAction.Uses
}
func isCommitHash(s string) bool {
return reCommitSHA.MatchString(s)
}
func hasServices(j *actionlint.Job) bool {
if j == nil {
return false
}
return j.Services != nil && len(j.Services.Value) > 0
}