github.com/cs3org/reva/v2@v2.27.7/pkg/auth/manager/nextcloud/nextcloud.go (about) 1 // Copyright 2018-2021 CERN 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 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 // Package nextcloud verifies a clientID and clientSecret against a Nextcloud backend. 20 package nextcloud 21 22 import ( 23 "context" 24 "encoding/json" 25 "io" 26 "net/http" 27 "strings" 28 29 authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" 30 user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 31 "github.com/cs3org/reva/v2/pkg/appctx" 32 "github.com/cs3org/reva/v2/pkg/auth" 33 "github.com/cs3org/reva/v2/pkg/auth/manager/registry" 34 "github.com/mitchellh/mapstructure" 35 "github.com/pkg/errors" 36 ) 37 38 func init() { 39 registry.Register("nextcloud", New) 40 } 41 42 // Manager is the Nextcloud-based implementation of the auth.Manager interface 43 // see https://github.com/cs3org/reva/blob/v1.13.0/pkg/auth/auth.go#L32-L35 44 type Manager struct { 45 client *http.Client 46 sharedSecret string 47 endPoint string 48 } 49 50 // AuthManagerConfig contains config for a Nextcloud-based AuthManager 51 type AuthManagerConfig struct { 52 EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"` 53 SharedSecret string `mapstructure:"shared_secret"` 54 MockHTTP bool `mapstructure:"mock_http"` 55 } 56 57 // Action describes a REST request to forward to the Nextcloud backend 58 type Action struct { 59 verb string 60 username string 61 argS string 62 } 63 64 func (c *AuthManagerConfig) init() { 65 } 66 67 func parseConfig(m map[string]interface{}) (*AuthManagerConfig, error) { 68 c := &AuthManagerConfig{} 69 if err := mapstructure.Decode(m, c); err != nil { 70 err = errors.Wrap(err, "error decoding conf") 71 return nil, err 72 } 73 return c, nil 74 } 75 76 // New returns an auth manager implementation that verifies against a Nextcloud backend. 77 func New(m map[string]interface{}) (auth.Manager, error) { 78 c, err := parseConfig(m) 79 if err != nil { 80 return nil, err 81 } 82 c.init() 83 84 return NewAuthManager(c) 85 } 86 87 // NewAuthManager returns a new Nextcloud-based AuthManager 88 func NewAuthManager(c *AuthManagerConfig) (*Manager, error) { 89 var client *http.Client 90 if c.MockHTTP { 91 // called := make([]string, 0) 92 // nextcloudServerMock := GetNextcloudServerMock(&called) 93 // client, _ = TestingHTTPClient(nextcloudServerMock) 94 95 // Wait for SetHTTPClient to be called later 96 client = nil 97 } else { 98 if len(c.EndPoint) == 0 { 99 return nil, errors.New("Please specify 'endpoint' in '[grpc.services.authprovider.auth_managers.nextcloud]'") 100 } 101 client = &http.Client{} 102 } 103 104 return &Manager{ 105 endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" 106 sharedSecret: c.SharedSecret, 107 client: client, 108 }, nil 109 } 110 111 // Configure method as defined in https://github.com/cs3org/reva/blob/v1.13.0/pkg/auth/auth.go#L32-L35 112 func (am *Manager) Configure(ml map[string]interface{}) error { 113 return nil 114 } 115 116 // SetHTTPClient sets the HTTP client 117 func (am *Manager) SetHTTPClient(c *http.Client) { 118 am.client = c 119 } 120 121 func (am *Manager) do(ctx context.Context, a Action) (int, []byte, error) { 122 log := appctx.GetLogger(ctx) 123 url := am.endPoint + "~" + a.username + "/api/auth/" + a.verb 124 log.Info().Msgf("am.do %s %s %s", url, a.argS, am.sharedSecret) 125 req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS)) 126 if err != nil { 127 return 0, nil, err 128 } 129 req.Header.Set("X-Reva-Secret", am.sharedSecret) 130 131 req.Header.Set("Content-Type", "application/json") 132 resp, err := am.client.Do(req) 133 if err != nil { 134 return 0, nil, err 135 } 136 137 defer resp.Body.Close() 138 body, err := io.ReadAll(resp.Body) 139 if err != nil { 140 return 0, nil, err 141 } 142 143 log.Info().Msgf("am.do response %d %s", resp.StatusCode, body) 144 return resp.StatusCode, body, nil 145 } 146 147 // Authenticate method as defined in https://github.com/cs3org/reva/blob/28500a8/pkg/auth/auth.go#L31-L33 148 func (am *Manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { 149 type paramsObj struct { 150 ClientID string `json:"clientID"` 151 ClientSecret string `json:"clientSecret"` 152 // Scope authpb.Scope 153 } 154 bodyObj := ¶msObj{ 155 ClientID: clientID, 156 ClientSecret: clientSecret, 157 // Scope: authpb.Scope{ 158 // Resource: &types.OpaqueEntry{ 159 // Decoder: "json", 160 // Value: []byte(`{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"}`), 161 // }, 162 // Role: authpb.Role_ROLE_OWNER, 163 // }, 164 } 165 bodyStr, err := json.Marshal(bodyObj) 166 if err != nil { 167 return nil, nil, err 168 } 169 log := appctx.GetLogger(ctx) 170 log.Info().Msgf("Authenticate %s %s", clientID, bodyStr) 171 172 statusCode, body, err := am.do(ctx, Action{"Authenticate", clientID, string(bodyStr)}) 173 174 if err != nil { 175 return nil, nil, err 176 } 177 178 if statusCode != 200 { 179 return nil, nil, errors.New("Username/password not recognized by Nextcloud backend") 180 } 181 182 type resultsObj struct { 183 User user.User `json:"user"` 184 Scopes map[string]*authpb.Scope `json:"scopes"` 185 } 186 result := &resultsObj{} 187 err = json.Unmarshal(body, &result) 188 if err != nil { 189 return nil, nil, err 190 } 191 var pointersMap = make(map[string]*authpb.Scope) 192 for k := range result.Scopes { 193 scope := result.Scopes[k] 194 pointersMap[k] = scope 195 } 196 return &result.User, pointersMap, nil 197 }