Skip to content

Commit 189dc68

Browse files
authored
Add machine wide mutex when authentication flow is in progress (#116)
* impl + tests Signed-off-by: HaniAlshikh <[email protected]> * silent was not so silent after all Signed-off-by: HaniAlshikh <[email protected]> Signed-off-by: HaniAlshikh <[email protected]>
1 parent efd2a9a commit 189dc68

File tree

7 files changed

+162
-5
lines changed

7 files changed

+162
-5
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ require (
88
github.com/golang/mock v1.6.0
99
github.com/golang/protobuf v1.5.2
1010
github.com/google/uuid v1.3.0
11+
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c
12+
github.com/juju/mutex/v2 v2.0.0
1113
github.com/kubism/testutil v0.1.0-alpha.2
1214
github.com/manifoldco/promptui v0.9.0
1315
github.com/olekukonko/tablewriter v0.0.5
@@ -61,6 +63,7 @@ require (
6163
github.com/inconshreveable/mousetrap v1.0.1 // indirect
6264
github.com/int128/listener v1.1.0 // indirect
6365
github.com/json-iterator/go v1.1.12 // indirect
66+
github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9 // indirect
6467
github.com/mattn/go-colorable v0.1.12 // indirect
6568
github.com/mattn/go-isatty v0.0.14 // indirect
6669
github.com/mattn/go-runewidth v0.0.13 // indirect

go.sum

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,19 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
495495
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
496496
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
497497
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
498+
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
499+
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
500+
github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271 h1:4R626WTwa7pRYQFiIRLVPepMhm05eZMEx+wIurRnMLc=
501+
github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9 h1:EJHbsNpQyupmMeWTq7inn+5L/WZ7JfzCVPJ+DP9McCQ=
502+
github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9/go.mod h1:TRm7EVGA3mQOqSVcBySRY7a9Y1/gyVhh/WTCnc5sD4U=
503+
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 h1:NO5tuyw++EGLnz56Q8KMyDZRwJwWO8jQnj285J3FOmY=
504+
github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208 h1:/WiCm+Vpj87e4QWuWwPD/bNE9kDrWCLvPBHOQNcG2+A=
505+
github.com/juju/mutex/v2 v2.0.0 h1:rVmJdOaXGWF8rjcFHBNd4x57/1tks5CgXHx55O55SB0=
506+
github.com/juju/mutex/v2 v2.0.0/go.mod h1:jwCfBs/smYDaeZLqeaCi8CB8M+tOes4yf827HoOEoqk=
507+
github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU=
508+
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 h1:XEDzpuZb8Ma7vLja3+5hzUqVTvAqm5Y+ygvnDs5iTMM=
509+
github.com/juju/utils/v3 v3.0.0-20220130232349-cd7ecef0e94a h1:5ZWDCeCF0RaITrZGemzmDFIhjR/MVSvBUqgSyaeTMbE=
510+
github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23 h1:wtEPbidt1VyHlb8RSztU6ySQj29FLsOQiI9XiJhXDM4=
498511
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
499512
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
500513
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -507,6 +520,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
507520
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
508521
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
509522
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
523+
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
510524
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
511525
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
512526
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
@@ -584,7 +598,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
584598
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
585599
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
586600
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
587-
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
588601
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
589602
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
590603
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@@ -1307,7 +1320,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
13071320
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
13081321
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
13091322
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1310-
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
1323+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
13111324
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
13121325
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
13131326
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

internal/spinner/spinner.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ var (
2525
Duration = 100 * time.Millisecond
2626
)
2727

28+
type Spinner struct {
29+
*spinner.Spinner
30+
}
31+
2832
// NewSpinner creates and starts the new default cli spinner. Usage:
29-
func NewSpinner() *spinner.Spinner {
33+
func NewSpinner() *Spinner {
3034
s := spinner.New(Charset, Duration) // Build our new spinner
3135
s.Start() // Start the spinner
32-
return s
36+
return &Spinner{s}
3337
}

internal/usecases/auth_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ var _ = Describe("render auth page", func() {
4747
)
4848

4949
It("should render the index page correctly", func() {
50-
5150
conf := config.NewConfig()
5251
conf.Server = expectedServer
5352
conf.AuthInformation = &config.AuthInformation{

internal/util/auth/auth.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ package auth
1717
import (
1818
"context"
1919
"fmt"
20+
"github.com/finleap-connect/monoctl/internal/spinner"
21+
"github.com/juju/clock"
22+
"github.com/juju/mutex/v2"
2023
"time"
2124

2225
"github.com/finleap-connect/monoctl/cmd/monoctl/flags"
@@ -26,6 +29,11 @@ import (
2629
"google.golang.org/grpc/status"
2730
)
2831

32+
const (
33+
authFlowNamedMutex = "monoctl-auth-flow"
34+
namedMutexDetectionTimeOut = 500 * time.Millisecond
35+
)
36+
2937
func RetryOnAuthFailSilently(ctx context.Context, configManager *config.ClientConfigManager, f func(ctx context.Context) error) error {
3038
return retryOnAuthFail(ctx, configManager, true, f)
3139
}
@@ -35,6 +43,13 @@ func RetryOnAuthFail(ctx context.Context, configManager *config.ClientConfigMana
3543
}
3644

3745
func retryOnAuthFail(ctx context.Context, configManager *config.ClientConfigManager, silent bool, f func(ctx context.Context) error) error {
46+
// Make sure no other process run the authentication flow.
47+
lock, err := acquireLock(ctx, silent)
48+
if err != nil {
49+
return err
50+
}
51+
defer lock.Release()
52+
3853
ctx, cancel := context.WithTimeout(ctx, time.Minute*2) // special timeout for login flow with consent can take longer
3954
defer cancel()
4055

@@ -61,3 +76,32 @@ func LoadConfigAndAuth(ctx context.Context, configManager *config.ClientConfigMa
6176
}
6277
return usecases.NewAuthUsecase(configManager, force, silent).Run(ctx)
6378
}
79+
80+
func acquireLock(_ context.Context, silent bool) (mutex.Releaser, error) {
81+
var s *spinner.Spinner
82+
if !silent {
83+
s = spinner.NewSpinner()
84+
defer s.Stop()
85+
}
86+
87+
acquired := make(chan struct{})
88+
defer close(acquired)
89+
go func() {
90+
select {
91+
case <-acquired:
92+
return
93+
case <-time.After(namedMutexDetectionTimeOut):
94+
if !silent {
95+
s.Stop()
96+
fmt.Printf("Another monoctl instance is already running the authentication flow...\n")
97+
s.Start()
98+
}
99+
}
100+
}()
101+
102+
return mutex.Acquire(mutex.Spec{
103+
Name: authFlowNamedMutex,
104+
Clock: clock.WallClock,
105+
Delay: time.Second,
106+
})
107+
}

internal/util/auth/auth_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2021 Monoskope Authors
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+
// 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+
package auth
16+
17+
import (
18+
"context"
19+
"os"
20+
"os/exec"
21+
"path/filepath"
22+
"time"
23+
24+
_ "embed"
25+
26+
. "github.com/onsi/ginkgo"
27+
. "github.com/onsi/gomega"
28+
"github.com/onsi/gomega/gbytes"
29+
"github.com/onsi/gomega/gexec"
30+
)
31+
32+
var _ = Describe("Auth", func() {
33+
Context("should not run multiple authentication flows simultaneously", func() {
34+
Specify("FirstProcess", func() {
35+
lock, err := acquireLock(context.Background(), false)
36+
Expect(err).ToNot(HaveOccurred())
37+
38+
ex, err := os.Executable()
39+
Expect(err).ToNot(HaveOccurred())
40+
err = os.Setenv("GINKGO_EDITOR_INTEGRATION", "true") // exit status 197 - Programmatic Focus
41+
Expect(err).ToNot(HaveOccurred())
42+
cmd := exec.Command(os.Args[0], "-ginkgo.focus", "SecondProcess", filepath.Dir(ex))
43+
cmd.Env = append(
44+
os.Environ(), // os.Environ() must be preserved on Windows otherwise "it will fail in weird and wonderful ways"
45+
"RUN_NAMED_MUTEX_TEST_HELPER=true",
46+
)
47+
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
48+
Expect(err).ToNot(HaveOccurred())
49+
50+
timeOut := 5 * time.Second // the default 1s is too fast for gexec.Start
51+
Eventually(session, timeOut).Should(gbytes.Say("Another monoctl instance is already running the authentication flow..."))
52+
53+
lock.Release()
54+
Eventually(session, timeOut).Should(gexec.Exit(0))
55+
})
56+
57+
Specify("SecondProcess", func() {
58+
skip := os.Getenv("RUN_NAMED_MUTEX_TEST_HELPER") != "true"
59+
if skip {
60+
Skip("This is to be run in a separate process")
61+
}
62+
63+
_, err := acquireLock(context.Background(), false)
64+
Expect(err).ToNot(HaveOccurred())
65+
})
66+
})
67+
})

internal/util/auth/suite_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2021 Monoskope Authors
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+
// 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+
package auth_test
16+
17+
import (
18+
"testing"
19+
20+
. "github.com/onsi/ginkgo"
21+
. "github.com/onsi/gomega"
22+
)
23+
24+
func TestAuth(t *testing.T) {
25+
RegisterFailHandler(Fail)
26+
RunSpecs(t, "Auth Util Suite")
27+
}

0 commit comments

Comments
 (0)