Merge branch '20300-rails7'
[arvados.git] / lib / controller / localdb / login_ldap_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package localdb
6
7 import (
8         "encoding/json"
9         "net"
10         "net/http"
11
12         "git.arvados.org/arvados.git/lib/controller/railsproxy"
13         "git.arvados.org/arvados.git/lib/ctrlctx"
14         "git.arvados.org/arvados.git/sdk/go/arvados"
15         "git.arvados.org/arvados.git/sdk/go/ctxlog"
16         "github.com/bradleypeabody/godap"
17         check "gopkg.in/check.v1"
18 )
19
20 var _ = check.Suite(&LDAPSuite{})
21
22 type LDAPSuite struct {
23         localdbSuite
24         ldap *godap.LDAPServer // fake ldap server that accepts auth goodusername/goodpassword
25 }
26
27 func (s *LDAPSuite) SetUpTest(c *check.C) {
28         s.localdbSuite.SetUpTest(c)
29
30         ln, err := net.Listen("tcp", "127.0.0.1:0")
31         c.Assert(err, check.IsNil)
32         s.ldap = &godap.LDAPServer{
33                 Listener: ln,
34                 Handlers: []godap.LDAPRequestHandler{
35                         &godap.LDAPBindFuncHandler{
36                                 LDAPBindFunc: func(binddn string, bindpw []byte) bool {
37                                         return binddn == "cn=goodusername,dc=example,dc=com" && string(bindpw) == "goodpassword"
38                                 },
39                         },
40                         &godap.LDAPSimpleSearchFuncHandler{
41                                 LDAPSimpleSearchFunc: func(req *godap.LDAPSimpleSearchRequest) []*godap.LDAPSimpleSearchResultEntry {
42                                         if req.FilterAttr != "uid" || req.BaseDN != "dc=example,dc=com" {
43                                                 return []*godap.LDAPSimpleSearchResultEntry{}
44                                         }
45                                         return []*godap.LDAPSimpleSearchResultEntry{
46                                                 {
47                                                         DN: "cn=" + req.FilterValue + "," + req.BaseDN,
48                                                         Attrs: map[string]interface{}{
49                                                                 "SN":   req.FilterValue,
50                                                                 "CN":   req.FilterValue,
51                                                                 "uid":  req.FilterValue,
52                                                                 "mail": req.FilterValue + "@example.com",
53                                                         },
54                                                 },
55                                         }
56                                 },
57                         },
58                 },
59         }
60         go func() {
61                 ctxlog.TestLogger(c).Print(s.ldap.Serve())
62         }()
63
64         s.cluster.Login.LDAP.Enable = true
65         err = json.Unmarshal([]byte(`"ldap://`+ln.Addr().String()+`"`), &s.cluster.Login.LDAP.URL)
66         c.Assert(err, check.IsNil)
67         s.cluster.Login.LDAP.StartTLS = false
68         s.cluster.Login.LDAP.SearchBindUser = "cn=goodusername,dc=example,dc=com"
69         s.cluster.Login.LDAP.SearchBindPassword = "goodpassword"
70         s.cluster.Login.LDAP.SearchBase = "dc=example,dc=com"
71         s.localdb.loginController = &ldapLoginController{
72                 Cluster: s.cluster,
73                 Parent:  s.localdb,
74         }
75 }
76
77 func (s *LDAPSuite) TestLoginSuccess(c *check.C) {
78         resp, err := s.localdb.UserAuthenticate(s.ctx, arvados.UserAuthenticateOptions{
79                 Username: "goodusername",
80                 Password: "goodpassword",
81         })
82         c.Check(err, check.IsNil)
83         c.Check(resp.APIToken, check.Not(check.Equals), "")
84         c.Check(resp.UUID, check.Matches, `zzzzz-gj3su-.*`)
85         c.Check(resp.Scopes, check.DeepEquals, []string{"all"})
86
87         ctx := ctrlctx.NewWithToken(s.ctx, s.cluster, "v2/"+resp.UUID+"/"+resp.APIToken)
88         user, err := railsproxy.NewConn(s.cluster).UserGetCurrent(ctx, arvados.GetOptions{})
89         c.Check(err, check.IsNil)
90         c.Check(user.Email, check.Equals, "goodusername@example.com")
91         c.Check(user.Username, check.Equals, "goodusername")
92 }
93
94 func (s *LDAPSuite) TestLoginFailure(c *check.C) {
95         // search returns no results
96         s.cluster.Login.LDAP.SearchBase = "dc=example,dc=invalid"
97         resp, err := s.localdb.UserAuthenticate(s.ctx, arvados.UserAuthenticateOptions{
98                 Username: "goodusername",
99                 Password: "goodpassword",
100         })
101         c.Check(err, check.ErrorMatches, `LDAP: Authentication failure \(with username "goodusername" and password\)`)
102         hs, ok := err.(interface{ HTTPStatus() int })
103         if c.Check(ok, check.Equals, true) {
104                 c.Check(hs.HTTPStatus(), check.Equals, http.StatusUnauthorized)
105         }
106         c.Check(resp.APIToken, check.Equals, "")
107
108         // search returns result, but auth fails
109         s.cluster.Login.LDAP.SearchBase = "dc=example,dc=com"
110         resp, err = s.localdb.UserAuthenticate(s.ctx, arvados.UserAuthenticateOptions{
111                 Username: "badusername",
112                 Password: "badpassword",
113         })
114         c.Check(err, check.ErrorMatches, `LDAP: Authentication failure \(with username "badusername" and password\)`)
115         hs, ok = err.(interface{ HTTPStatus() int })
116         if c.Check(ok, check.Equals, true) {
117                 c.Check(hs.HTTPStatus(), check.Equals, http.StatusUnauthorized)
118         }
119         c.Check(resp.APIToken, check.Equals, "")
120 }