// RAINBOND, Application Management Platform // Copyright (C) 2014-2017 Goodrain Co., Ltd. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. For any non-GPL usage of Rainbond, // one or multiple Commercial Licenses authorized by Goodrain Co., Ltd. // must be obtained first. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . package registry import ( "encoding/json" "fmt" "net/http" "net/url" ) type TokenTransport struct { Transport http.RoundTripper Username string Password string } func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) { resp, err := t.Transport.RoundTrip(req) if err != nil { return resp, err } if authService := isTokenDemand(resp); authService != nil { resp, err = t.authAndRetry(authService, req) } return resp, err } type authToken struct { Token string `json:"token"` } func (t *TokenTransport) authAndRetry(authService *authService, req *http.Request) (*http.Response, error) { token, authResp, err := t.auth(authService) if err != nil { return authResp, err } retryResp, err := t.retry(req, token) return retryResp, err } func (t *TokenTransport) auth(authService *authService) (string, *http.Response, error) { authReq, err := authService.Request(t.Username, t.Password) if err != nil { return "", nil, err } client := http.Client{ Transport: t.Transport, } response, err := client.Do(authReq) if err != nil { return "", nil, err } if response.StatusCode != http.StatusOK { return "", response, err } defer response.Body.Close() var authToken authToken decoder := json.NewDecoder(response.Body) err = decoder.Decode(&authToken) if err != nil { return "", nil, err } return authToken.Token, nil, nil } func (t *TokenTransport) retry(req *http.Request, token string) (*http.Response, error) { req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) resp, err := t.Transport.RoundTrip(req) return resp, err } type authService struct { Realm string Service string Scope string } func (authService *authService) Request(username, password string) (*http.Request, error) { url, err := url.Parse(authService.Realm) if err != nil { return nil, err } q := url.Query() q.Set("service", authService.Service) if authService.Scope != "" { q.Set("scope", authService.Scope) } url.RawQuery = q.Encode() request, err := http.NewRequest("GET", url.String(), nil) if username != "" || password != "" { request.SetBasicAuth(username, password) } return request, err } func isTokenDemand(resp *http.Response) *authService { if resp == nil { return nil } if resp.StatusCode != http.StatusUnauthorized { return nil } return parseOauthHeader(resp) } func parseOauthHeader(resp *http.Response) *authService { challenges := parseAuthHeader(resp.Header) for _, challenge := range challenges { if challenge.Scheme == "bearer" { return &authService{ Realm: challenge.Parameters["realm"], Service: challenge.Parameters["service"], Scope: challenge.Parameters["scope"], } } } return nil }