summaryrefslogtreecommitdiff
path: root/internal/command/twofactorrecover
diff options
context:
space:
mode:
authorNick Thomas <nick@gitlab.com>2019-10-17 12:04:52 +0100
committerNick Thomas <nick@gitlab.com>2019-10-18 11:47:25 +0100
commit83d11f4deeb20b852a0af3433190a0f7250a0027 (patch)
tree1a9df18d6f9f59712c6f5c98e995a4918eb94a11 /internal/command/twofactorrecover
parent7d5229db263a62661653431881bef8b46984d0de (diff)
downloadgitlab-shell-83d11f4deeb20b852a0af3433190a0f7250a0027.tar.gz
Move go code up one level
Diffstat (limited to 'internal/command/twofactorrecover')
-rw-r--r--internal/command/twofactorrecover/twofactorrecover.go65
-rw-r--r--internal/command/twofactorrecover/twofactorrecover_test.go136
2 files changed, 201 insertions, 0 deletions
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
new file mode 100644
index 0000000..c68080a
--- /dev/null
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -0,0 +1,65 @@
+package twofactorrecover
+
+import (
+ "fmt"
+ "strings"
+
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/readwriter"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/config"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/twofactorrecover"
+)
+
+type Command struct {
+ Config *config.Config
+ Args *commandargs.Shell
+ ReadWriter *readwriter.ReadWriter
+}
+
+func (c *Command) Execute() error {
+ if c.canContinue() {
+ c.displayRecoveryCodes()
+ } else {
+ fmt.Fprintln(c.ReadWriter.Out, "\nNew recovery codes have *not* been generated. Existing codes will remain valid.")
+ }
+
+ return nil
+}
+
+func (c *Command) canContinue() bool {
+ question :=
+ "Are you sure you want to generate new two-factor recovery codes?\n" +
+ "Any existing recovery codes you saved will be invalidated. (yes/no)"
+ fmt.Fprintln(c.ReadWriter.Out, question)
+
+ var answer string
+ fmt.Fscanln(c.ReadWriter.In, &answer)
+
+ return answer == "yes"
+}
+
+func (c *Command) displayRecoveryCodes() {
+ codes, err := c.getRecoveryCodes()
+
+ if err == nil {
+ messageWithCodes :=
+ "\nYour two-factor authentication recovery codes are:\n\n" +
+ strings.Join(codes, "\n") +
+ "\n\nDuring sign in, use one of the codes above when prompted for\n" +
+ "your two-factor code. Then, visit your Profile Settings and add\n" +
+ "a new device so you do not lose access to your account again.\n"
+ fmt.Fprint(c.ReadWriter.Out, messageWithCodes)
+ } else {
+ fmt.Fprintf(c.ReadWriter.Out, "\nAn error occurred while trying to generate new recovery codes.\n%v\n", err)
+ }
+}
+
+func (c *Command) getRecoveryCodes() ([]string, error) {
+ client, err := twofactorrecover.NewClient(c.Config)
+
+ if err != nil {
+ return nil, err
+ }
+
+ return client.GetRecoveryCodes(c.Args)
+}
diff --git a/internal/command/twofactorrecover/twofactorrecover_test.go b/internal/command/twofactorrecover/twofactorrecover_test.go
new file mode 100644
index 0000000..291d499
--- /dev/null
+++ b/internal/command/twofactorrecover/twofactorrecover_test.go
@@ -0,0 +1,136 @@
+package twofactorrecover
+
+import (
+ "bytes"
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/readwriter"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/config"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/testserver"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/twofactorrecover"
+)
+
+var (
+ requests []testserver.TestRequestHandler
+)
+
+func setup(t *testing.T) {
+ requests = []testserver.TestRequestHandler{
+ {
+ Path: "/api/v4/internal/two_factor_recovery_codes",
+ Handler: func(w http.ResponseWriter, r *http.Request) {
+ b, err := ioutil.ReadAll(r.Body)
+ defer r.Body.Close()
+
+ require.NoError(t, err)
+
+ var requestBody *twofactorrecover.RequestBody
+ json.Unmarshal(b, &requestBody)
+
+ switch requestBody.KeyId {
+ case "1":
+ body := map[string]interface{}{
+ "success": true,
+ "recovery_codes": [2]string{"recovery", "codes"},
+ }
+ json.NewEncoder(w).Encode(body)
+ case "forbidden":
+ body := map[string]interface{}{
+ "success": false,
+ "message": "Forbidden!",
+ }
+ json.NewEncoder(w).Encode(body)
+ case "broken":
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ },
+ },
+ }
+}
+
+const (
+ question = "Are you sure you want to generate new two-factor recovery codes?\n" +
+ "Any existing recovery codes you saved will be invalidated. (yes/no)\n\n"
+ errorHeader = "An error occurred while trying to generate new recovery codes.\n"
+)
+
+func TestExecute(t *testing.T) {
+ setup(t)
+
+ url, cleanup := testserver.StartSocketHttpServer(t, requests)
+ defer cleanup()
+
+ testCases := []struct {
+ desc string
+ arguments *commandargs.Shell
+ answer string
+ expectedOutput string
+ }{
+ {
+ desc: "With a known key id",
+ arguments: &commandargs.Shell{GitlabKeyId: "1"},
+ answer: "yes\n",
+ expectedOutput: question +
+ "Your two-factor authentication recovery codes are:\n\nrecovery\ncodes\n\n" +
+ "During sign in, use one of the codes above when prompted for\n" +
+ "your two-factor code. Then, visit your Profile Settings and add\n" +
+ "a new device so you do not lose access to your account again.\n",
+ },
+ {
+ desc: "With bad response",
+ arguments: &commandargs.Shell{GitlabKeyId: "-1"},
+ answer: "yes\n",
+ expectedOutput: question + errorHeader + "Parsing failed\n",
+ },
+ {
+ desc: "With API returns an error",
+ arguments: &commandargs.Shell{GitlabKeyId: "forbidden"},
+ answer: "yes\n",
+ expectedOutput: question + errorHeader + "Forbidden!\n",
+ },
+ {
+ desc: "With API fails",
+ arguments: &commandargs.Shell{GitlabKeyId: "broken"},
+ answer: "yes\n",
+ expectedOutput: question + errorHeader + "Internal API error (500)\n",
+ },
+ {
+ desc: "With missing arguments",
+ arguments: &commandargs.Shell{},
+ answer: "yes\n",
+ expectedOutput: question + errorHeader + "who='' is invalid\n",
+ },
+ {
+ desc: "With negative answer",
+ arguments: &commandargs.Shell{},
+ answer: "no\n",
+ expectedOutput: question +
+ "New recovery codes have *not* been generated. Existing codes will remain valid.\n",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ output := &bytes.Buffer{}
+ input := bytes.NewBufferString(tc.answer)
+
+ cmd := &Command{
+ Config: &config.Config{GitlabUrl: url},
+ Args: tc.arguments,
+ ReadWriter: &readwriter.ReadWriter{Out: output, In: input},
+ }
+
+ err := cmd.Execute()
+
+ assert.NoError(t, err)
+ assert.Equal(t, tc.expectedOutput, output.String())
+ })
+ }
+}