Skip to content

Commit

Permalink
feat(api/v1): Add new Docker registry client
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanilves committed Aug 12, 2018
1 parent 6913c21 commit 4dbf691
Show file tree
Hide file tree
Showing 14 changed files with 608 additions and 496 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ changelog:
sed -r "s|^([0-9a-f]{7,}) (.*)|* [\`\1\`](${GITHUB_COMMIT_URL}/\1) \2|"

release: clean
release: LAST_BUILD_NUMBER:=$(shell git tag --sort=creatordate | tail -n1 | sed 's/^v.*\.//')
release: LAST_BUILD_NUMBER:=$(shell git tag --sort=creatordate | grep "^v${API_VERSION}\." | tail -n1 | sed 's/^v.*\.//')
release: THIS_BUILD_NUMBER:=$(shell expr ${LAST_BUILD_NUMBER} + 1)
release: THIS_RELEASE_NAME:=v${API_VERSION}.${THIS_BUILD_NUMBER}
release:
Expand Down
2 changes: 1 addition & 1 deletion api/v1/collection/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func makeTags() []*tag.Tag {
var tags = make([]*tag.Tag, 0)

for name, digest := range seed {
tg, _ := tag.New(name, digest, tag.Options{})
tg, _ := tag.New(name, tag.Options{Digest: digest})

tags = append(tags, tg)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,34 @@ import (
"strings"
)

// TokenResponse implementation for Basic authentication
type TokenResponse struct {
// Token implementation for Basic authentication
type Token struct {
T string
}

// Method is set to "Basic"
func (tr TokenResponse) Method() string {
func (tk Token) Method() string {
return "Basic"
}

// Token Basic token
func (tr TokenResponse) Token() string {
return tr.T
// String form of Basic token
func (tk Token) String() string {
return tk.T
}

// ExpiresIn is set to 0 for Basic authentication
func (tr TokenResponse) ExpiresIn() int {
func (tk Token) ExpiresIn() int {
return 0
}

// AuthHeader returns contents of the Authorization HTTP header
func (tr TokenResponse) AuthHeader() string {
return tr.Method() + " " + tr.Token()
}

func getTokenFromHeader(header string) string {
fields := strings.Split(header, " ")

return fields[1]
}

// RequestToken performs Basic authentication and extracts token from response header
func RequestToken(url, username, password string) (*TokenResponse, error) {
func RequestToken(url, username, password string) (*Token, error) {
hc := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
Expand All @@ -55,5 +50,5 @@ func RequestToken(url, username, password string) (*TokenResponse, error) {
return nil, errors.New("[AUTH::BASIC] Bad response status: " + resp.Status + " >> " + url)
}

return &TokenResponse{T: getTokenFromHeader(req.Header["Authorization"][0])}, nil
return &Token{T: getTokenFromHeader(req.Header["Authorization"][0])}, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,41 @@ import (
"net/http"
)

// TokenResponse implementation for Bearer authentication
type TokenResponse struct {
// Token implementation for Bearer authentication
type Token struct {
T string `json:"token"`
E int `json:"expires_in"`
}

// Method is set to "Bearer"
func (tr TokenResponse) Method() string {
func (tk Token) Method() string {
return "Bearer"
}

// Token Bearer token
func (tr TokenResponse) Token() string {
return tr.T
// String form of Bearer token
func (tk Token) String() string {
return tk.T
}

// ExpiresIn token lifetime in seconds
func (tr TokenResponse) ExpiresIn() int {
return tr.E
func (tk Token) ExpiresIn() int {
return tk.E
}

// AuthHeader returns contents of the Authorization HTTP header
func (tr TokenResponse) AuthHeader() string {
return tr.Method() + " " + tr.Token()
}

func decodeTokenResponse(data io.ReadCloser) (*TokenResponse, error) {
tr := TokenResponse{}
func decodeTokenResponse(data io.ReadCloser) (*Token, error) {
tk := Token{}

err := json.NewDecoder(data).Decode(&tr)
err := json.NewDecoder(data).Decode(&tk)
if err != nil {
return nil, err
}

return &tr, nil
return &tk, nil
}

// RequestToken requests Bearer token from authentication service
func RequestToken(realm, service, repoPath, username, password string) (*TokenResponse, error) {
url := realm + "?service=" + service + "&scope=repository:" + repoPath + ":pull"
func RequestToken(username, password string, params map[string]string) (*Token, error) {
url := params["realm"] + "?service=" + params["service"] + "&scope=" + params["scope"]

hc := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
Expand Down
25 changes: 25 additions & 0 deletions api/v1/registry/client/auth/none/none.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package none

// Token implementation for None authentication
type Token struct {
}

// Method is set to "None"
func (tk Token) Method() string {
return "None"
}

// String is empty (no token here)
func (tk Token) String() string {
return ""
}

// ExpiresIn is set to 0 for None authentication
func (tk Token) ExpiresIn() int {
return 0
}

// RequestToken does pretty little here...
func RequestToken() (*Token, error) {
return &Token{}, nil
}
92 changes: 92 additions & 0 deletions api/v1/registry/client/auth/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package auth

import (
"errors"
"net/http"
"strings"

log "github.com/sirupsen/logrus"

"github.com/ivanilves/lstags/api/v1/registry/client/auth/basic"
"github.com/ivanilves/lstags/api/v1/registry/client/auth/bearer"
"github.com/ivanilves/lstags/api/v1/registry/client/auth/none"
)

// Token is an abstraction for aggregated token-related information we get from authentication services
type Token interface {
Method() string
String() string
ExpiresIn() int
}

type authHeader string

func extractAuthHeader(hh []string) (authHeader, error) {
if len(hh) == 0 {
return "None realm=none", nil
}

h := hh[0]

if len(strings.SplitN(h, " ", 2)) != 2 {
return "", errors.New("Unexpected 'Www-Authenticate' header: " + h)
}

return authHeader(h), nil
}

func getAuthMethod(h authHeader) string {
return strings.SplitN(string(h), " ", 2)[0]
}

func getAuthParams(h authHeader) map[string]string {
params := make(map[string]string)

paramString := strings.SplitN(string(h), " ", 2)[1]

for _, keyValueString := range strings.Split(paramString, ",") {
kv := strings.Split(keyValueString, "=")
if len(kv) == 2 {
params[kv[0]] = strings.Trim(kv[1], "\"")
}
}

return params
}

// NewToken creates a new instance of Token in two steps:
// * detects authentication type ("Bearer", "Basic" or "None")
// * delegates actual authentication to the type-specific implementation
func NewToken(url, username, password, scope string) (Token, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}

authHeader, err := extractAuthHeader(resp.Header["Www-Authenticate"])
if err != nil {
return nil, err
}

method := getAuthMethod(authHeader)
params := getAuthParams(authHeader)

switch method {
case "None":
return none.RequestToken()
case "Basic":
t, err := basic.RequestToken(url, username, password)
if err != nil {
log.Debug(err.Error())

return none.RequestToken()
}

return t, nil
case "Bearer":
params["scope"] = scope
return bearer.RequestToken(username, password, params)
default:
return nil, errors.New("Unknown authentication method: " + method)
}
}
Loading

0 comments on commit 4dbf691

Please sign in to comment.