oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/registry/remote/credentials/registry.go (about)

     1  /*
     2  Copyright The ORAS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package credentials
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  
    23  	"oras.land/oras-go/v2/registry/remote"
    24  	"oras.land/oras-go/v2/registry/remote/auth"
    25  )
    26  
    27  // ErrClientTypeUnsupported is thrown by Login() when the registry's client type
    28  // is not supported.
    29  var ErrClientTypeUnsupported = errors.New("client type not supported")
    30  
    31  // Login provides the login functionality with the given credentials. The target
    32  // registry's client should be nil or of type *auth.Client. Login uses
    33  // a client local to the function and will not modify the original client of
    34  // the registry.
    35  func Login(ctx context.Context, store Store, reg *remote.Registry, cred auth.Credential) error {
    36  	// create a clone of the original registry for login purpose
    37  	regClone := *reg
    38  	// we use the original client if applicable, otherwise use a default client
    39  	var authClient auth.Client
    40  	if reg.Client == nil {
    41  		authClient = *auth.DefaultClient
    42  		authClient.Cache = nil // no cache
    43  	} else if client, ok := reg.Client.(*auth.Client); ok {
    44  		authClient = *client
    45  	} else {
    46  		return ErrClientTypeUnsupported
    47  	}
    48  	regClone.Client = &authClient
    49  	// update credentials with the client
    50  	authClient.Credential = auth.StaticCredential(reg.Reference.Registry, cred)
    51  	// validate and store the credential
    52  	if err := regClone.Ping(ctx); err != nil {
    53  		return fmt.Errorf("failed to validate the credentials for %s: %w", regClone.Reference.Registry, err)
    54  	}
    55  	hostname := ServerAddressFromRegistry(regClone.Reference.Registry)
    56  	if err := store.Put(ctx, hostname, cred); err != nil {
    57  		return fmt.Errorf("failed to store the credentials for %s: %w", hostname, err)
    58  	}
    59  	return nil
    60  }
    61  
    62  // Logout provides the logout functionality given the registry name.
    63  func Logout(ctx context.Context, store Store, registryName string) error {
    64  	registryName = ServerAddressFromRegistry(registryName)
    65  	if err := store.Delete(ctx, registryName); err != nil {
    66  		return fmt.Errorf("failed to delete the credential for %s: %w", registryName, err)
    67  	}
    68  	return nil
    69  }
    70  
    71  // Credential returns a Credential() function that can be used by auth.Client.
    72  func Credential(store Store) auth.CredentialFunc {
    73  	return func(ctx context.Context, hostport string) (auth.Credential, error) {
    74  		hostport = ServerAddressFromHostname(hostport)
    75  		if hostport == "" {
    76  			return auth.EmptyCredential, nil
    77  		}
    78  		return store.Get(ctx, hostport)
    79  	}
    80  }
    81  
    82  // ServerAddressFromRegistry maps a registry to a server address, which is used as
    83  // a key for credentials store. The Docker CLI expects that the credentials of
    84  // the registry 'docker.io' will be added under the key "https://index.docker.io/v1/".
    85  // See: https://github.com/moby/moby/blob/v24.0.2/registry/config.go#L25-L48
    86  func ServerAddressFromRegistry(registry string) string {
    87  	if registry == "docker.io" {
    88  		return "https://index.docker.io/v1/"
    89  	}
    90  	return registry
    91  }
    92  
    93  // ServerAddressFromHostname maps a hostname to a server address, which is used as
    94  // a key for credentials store. It is expected that the traffic targetting the
    95  // host "registry-1.docker.io" will be redirected to "https://index.docker.io/v1/".
    96  // See: https://github.com/moby/moby/blob/v24.0.2/registry/config.go#L25-L48
    97  func ServerAddressFromHostname(hostname string) string {
    98  	if hostname == "registry-1.docker.io" {
    99  		return "https://index.docker.io/v1/"
   100  	}
   101  	return hostname
   102  }