github.com/actions-on-google/gactions@v3.2.0+incompatible/cmd/gactions/cli/decrypt/decrypt.go (about)

     1  // Copyright 2020 Google LLC
     2  //
     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  //     https://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  // Package decrypt provides an implementation of "gactions decrypt" command.
    16  package decrypt
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"runtime"
    25  	"strings"
    26  
    27  	"github.com/actions-on-google/gactions/api/sdk"
    28  	"github.com/actions-on-google/gactions/log"
    29  	"github.com/actions-on-google/gactions/project"
    30  	"github.com/spf13/cobra"
    31  	"gopkg.in/yaml.v2"
    32  )
    33  
    34  func parseClientSecret(files map[string][]byte) (string, error) {
    35  	type secretFile struct {
    36  		EncryptedClientSecret string `yaml:"encryptedClientSecret"`
    37  	}
    38  	in, ok := files["settings/accountLinkingSecret.yaml"]
    39  	if !ok {
    40  		log.Infoln("accountLinkingSecret.yaml not found in project files")
    41  		return "", errors.New("accountLinkingSecret.yaml not found in project files. " +
    42  			"Try encrypting your client secret first, or pulling an existing project with a client secret")
    43  	}
    44  	f := secretFile{}
    45  	if err := yaml.Unmarshal(in, &f); err != nil {
    46  		return "", err
    47  	}
    48  	return f.EncryptedClientSecret, nil
    49  }
    50  
    51  func expandTilde(p string) (string, error) {
    52  	if !strings.HasPrefix(p, "~/") || runtime.GOOS == "windows" {
    53  		return p, nil
    54  	}
    55  	home, err := os.UserHomeDir()
    56  	if err != nil {
    57  		return p, err
    58  	}
    59  	return filepath.Join(home, p[2:]), nil
    60  }
    61  
    62  func normPath(p, root string) string {
    63  	norm := filepath.FromSlash(p)
    64  	if strings.HasPrefix(p, "~/") {
    65  		t, err := expandTilde(p)
    66  		if err == nil {
    67  			norm = t
    68  		}
    69  	}
    70  	if strings.HasPrefix(p, "./") || strings.HasPrefix(p, "../") || strings.HasPrefix(p, ".\\") || strings.HasPrefix(p, "..\\") {
    71  		norm = filepath.Clean(filepath.Join(root, p))
    72  	}
    73  	if !filepath.IsAbs(norm) {
    74  		norm = filepath.Join(root, p)
    75  	}
    76  	return norm
    77  }
    78  
    79  // AddCommand adds decrypt sub-command to the passed in root command.
    80  func AddCommand(ctx context.Context, root *cobra.Command, proj project.Project) {
    81  	decrypt := &cobra.Command{
    82  		Use:   "decrypt <plaint-text-file>",
    83  		Short: "Decrypt client secret.",
    84  		Long:  "This command decrypts the client secret key used in Account Linking. Specify a file path for the decrypt output. This can be a relative or absolute path.",
    85  		RunE: func(cmd *cobra.Command, args []string) error {
    86  			if proj.ProjectRoot() == "" {
    87  				log.Errorf(`Can't find a project root. This may be because (1) %q was not found in this or any of the parent folders, or (2) if %q was found, but the key "sdkPath" was missing, or (3) if %q and manifest.yaml were both not found.`, project.ConfigName, project.ConfigName, project.ConfigName)
    88  				return errors.New("can not determine project root")
    89  			}
    90  			files, err := proj.Files()
    91  			if err != nil {
    92  				return err
    93  			}
    94  			s, err := parseClientSecret(files)
    95  			if err != nil {
    96  				return err
    97  			}
    98  			out := normPath(args[0], proj.ProjectRoot())
    99  			return sdk.DecryptSecretJSON(ctx, proj, s, out)
   100  		},
   101  		Args: func(cmd *cobra.Command, args []string) error {
   102  			if len(args) > 1 {
   103  				return fmt.Errorf("unexpected arguments: %v", args)
   104  			}
   105  			if len(args) < 1 {
   106  				return fmt.Errorf(`<plain-text-file> argument is missing. Try "gactions decrypt <pathToPlainTextFile>"`)
   107  			}
   108  			return nil
   109  		},
   110  	}
   111  	root.AddCommand(decrypt)
   112  }