github.com/jancarloviray/community@v0.41.1-0.20170124221257-33a66c87cf2f/core/api/endpoint/authentication_endpoint.go (about) 1 // Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved. 2 // 3 // This software (Documize Community Edition) is licensed under 4 // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html 5 // 6 // You can operate outside the AGPL restrictions by purchasing 7 // Documize Enterprise Edition and obtaining a commercial license 8 // by contacting <sales@documize.com>. 9 // 10 // https://documize.com 11 12 package endpoint 13 14 import ( 15 "crypto/rand" 16 "database/sql" 17 "encoding/json" 18 "errors" 19 "fmt" 20 "net/http" 21 "strings" 22 "time" 23 24 jwt "github.com/dgrijalva/jwt-go" 25 26 "github.com/documize/community/core/api/endpoint/models" 27 "github.com/documize/community/core/api/entity" 28 "github.com/documize/community/core/api/request" 29 "github.com/documize/community/core/api/util" 30 "github.com/documize/community/core/environment" 31 "github.com/documize/community/core/log" 32 "github.com/documize/community/core/section/provider" 33 "github.com/documize/community/core/utility" 34 "github.com/documize/community/core/web" 35 ) 36 37 // Authenticate user based up HTTP Authorization header. 38 // An encrypted authentication token is issued with an expiry date. 39 func Authenticate(w http.ResponseWriter, r *http.Request) { 40 method := "Authenticate" 41 p := request.GetPersister(r) 42 43 authHeader := r.Header.Get("Authorization") 44 45 // check for http header 46 if len(authHeader) == 0 { 47 writeBadRequestError(w, method, "Missing Authorization header") 48 return 49 } 50 51 // decode what we received 52 data := strings.Replace(authHeader, "Basic ", "", 1) 53 decodedBytes, err := utility.DecodeBase64([]byte(data)) 54 55 if err != nil { 56 writeBadRequestError(w, method, "Unable to decode authentication token") 57 return 58 } 59 60 // check that we have domain:email:password (but allow for : in password field!) 61 decoded := string(decodedBytes) 62 credentials := strings.SplitN(decoded, ":", 3) 63 64 if len(credentials) != 3 { 65 writeBadRequestError(w, method, "Bad authentication token, expecting domain:email:password") 66 return 67 } 68 69 domain := strings.TrimSpace(strings.ToLower(credentials[0])) 70 domain = request.CheckDomain(domain) // TODO optimize by removing this once js allows empty domains 71 email := strings.TrimSpace(strings.ToLower(credentials[1])) 72 password := credentials[2] 73 log.Info("logon attempt " + email + " @ " + domain) 74 75 user, err := p.GetUserByDomain(domain, email) 76 77 if err == sql.ErrNoRows { 78 writeUnauthorizedError(w) 79 return 80 } 81 82 if err != nil { 83 writeServerError(w, method, err) 84 return 85 } 86 87 if !user.Active || len(user.Reset) > 0 || len(user.Password) == 0 { 88 writeUnauthorizedError(w) 89 return 90 } 91 92 // Password correct and active user 93 if email != strings.TrimSpace(strings.ToLower(user.Email)) || !util.MatchPassword(user.Password, password, user.Salt) { 94 writeUnauthorizedError(w) 95 return 96 } 97 98 org, err := p.GetOrganizationByDomain(domain) 99 100 if err != nil { 101 writeUnauthorizedError(w) 102 return 103 } 104 105 // Attach user accounts and work out permissions 106 attachUserAccounts(p, org.RefID, &user) 107 108 if len(user.Accounts) == 0 { 109 writeUnauthorizedError(w) 110 return 111 } 112 113 authModel := models.AuthenticationModel{} 114 authModel.Token = generateJWT(user.RefID, org.RefID, domain) 115 authModel.User = user 116 117 json, err := json.Marshal(authModel) 118 119 if err != nil { 120 writeJSONMarshalError(w, method, "user", err) 121 return 122 } 123 124 writeSuccessBytes(w, json) 125 } 126 127 // Authorize secure API calls by inspecting authentication token. 128 // request.Context provides caller user information. 129 // Site meta sent back as HTTP custom headers. 130 func Authorize(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 131 method := "Authorize" 132 133 // Let certain requests pass straight through 134 authenticated := preAuthorizeStaticAssets(r) 135 136 if !authenticated { 137 token := findJWT(r) 138 hasToken := len(token) > 1 139 context, _, tokenErr := decodeJWT(token) 140 141 var org = entity.Organization{} 142 var err = errors.New("") 143 144 // We always grab the org record regardless of token status. 145 // Why? If bad token we might be OK to alow anonymous access 146 // depending upon the domain in question. 147 p := request.GetPersister(r) 148 149 if len(context.OrgID) == 0 { 150 org, err = p.GetOrganizationByDomain(request.GetRequestSubdomain(r)) 151 } else { 152 org, err = p.GetOrganization(context.OrgID) 153 } 154 155 context.Subdomain = org.Domain 156 157 // Inability to find org record spells the end of this request. 158 if err != nil { 159 writeForbiddenError(w) 160 return 161 } 162 163 // If we have bad auth token and the domain does not allow anon access 164 if !org.AllowAnonymousAccess && tokenErr != nil { 165 writeUnauthorizedError(w) 166 return 167 } 168 169 domain := request.GetSubdomainFromHost(r) 170 domain2 := request.GetRequestSubdomain(r) 171 if org.Domain != domain && org.Domain != domain2 { 172 log.Info(fmt.Sprintf("domain mismatch %s vs. %s vs. %s", domain, domain2, org.Domain)) 173 174 writeUnauthorizedError(w) 175 return 176 } 177 178 // If we have bad auth token and the domain allows anon access 179 // then we generate guest context. 180 if org.AllowAnonymousAccess { 181 // So you have a bad token 182 if hasToken { 183 if tokenErr != nil { 184 writeUnauthorizedError(w) 185 return 186 } 187 } else { 188 // Just grant anon user guest access 189 context.UserID = "0" 190 context.OrgID = org.RefID 191 context.Authenticated = false 192 context.Guest = true 193 } 194 } 195 196 // Refresh context and persister 197 request.SetContext(r, context) 198 p = request.GetPersister(r) 199 200 context.AllowAnonymousAccess = org.AllowAnonymousAccess 201 context.OrgName = org.Title 202 context.Administrator = false 203 context.Editor = false 204 context.Global = false 205 206 // Fetch user permissions for this org 207 if context.Authenticated { 208 user, err := getSecuredUser(p, org.RefID, context.UserID) 209 210 if err != nil { 211 writeServerError(w, method, err) 212 return 213 } 214 215 context.Administrator = user.Admin 216 context.Editor = user.Editor 217 context.Global = user.Global 218 } 219 220 request.SetContext(r, context) 221 p = request.GetPersister(r) 222 223 // Middleware moves on if we say 'yes' -- autheticated or allow anon access. 224 authenticated = context.Authenticated || org.AllowAnonymousAccess 225 } 226 227 if authenticated { 228 next(w, r) 229 } else { 230 w.WriteHeader(http.StatusUnauthorized) 231 } 232 } 233 234 // ValidateAuthToken checks the auth token and returns the corresponding user. 235 func ValidateAuthToken(w http.ResponseWriter, r *http.Request) { 236 237 // TODO should this go after token validation? 238 if s := r.URL.Query().Get("section"); s != "" { 239 if err := provider.Callback(s, w, r); err != nil { 240 log.Error("section validation failure", err) 241 w.WriteHeader(http.StatusUnauthorized) 242 } 243 return 244 } 245 246 method := "ValidateAuthToken" 247 248 context, claims, err := decodeJWT(findJWT(r)) 249 250 if err != nil { 251 log.Error("token validation", err) 252 w.WriteHeader(http.StatusUnauthorized) 253 return 254 } 255 256 request.SetContext(r, context) 257 p := request.GetPersister(r) 258 259 org, err := p.GetOrganization(context.OrgID) 260 261 if err != nil { 262 log.Error("token validation", err) 263 w.WriteHeader(http.StatusUnauthorized) 264 return 265 } 266 267 domain := request.GetSubdomainFromHost(r) 268 269 if org.Domain != domain || claims["domain"] != domain { 270 log.Error("token validation", err) 271 w.WriteHeader(http.StatusUnauthorized) 272 return 273 } 274 275 user, err := getSecuredUser(p, context.OrgID, context.UserID) 276 277 if err != nil { 278 log.Error("get user error for token validation", err) 279 w.WriteHeader(http.StatusUnauthorized) 280 return 281 } 282 283 json, err := json.Marshal(user) 284 285 if err != nil { 286 writeJSONMarshalError(w, method, "user", err) 287 return 288 } 289 290 writeSuccessBytes(w, json) 291 } 292 293 // Certain assets/URL do not require authentication. 294 // Just stops the log files being clogged up with failed auth errors. 295 func preAuthorizeStaticAssets(r *http.Request) bool { 296 if strings.ToLower(r.URL.Path) == "/" || 297 strings.ToLower(r.URL.Path) == "/validate" || 298 strings.ToLower(r.URL.Path) == "/favicon.ico" || 299 strings.ToLower(r.URL.Path) == "/robots.txt" || 300 strings.ToLower(r.URL.Path) == "/version" || 301 strings.HasPrefix(strings.ToLower(r.URL.Path), "/api/public/") || 302 ((web.SiteMode == web.SiteModeSetup) && (strings.ToLower(r.URL.Path) == "/api/setup")) { 303 304 return true 305 } 306 307 return false 308 } 309 310 var jwtKey string 311 312 func init() { 313 environment.GetString(&jwtKey, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated", 314 func(t *string, n string) bool { 315 if jwtKey == "" { 316 b := make([]byte, 17) 317 _, err := rand.Read(b) 318 if err != nil { 319 jwtKey = err.Error() 320 log.Error("problem using crypto/rand", err) 321 return false 322 } 323 for k, v := range b { 324 if (v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z') || (v >= '0' && v <= '0') { 325 b[k] = v 326 } else { 327 s := fmt.Sprintf("%x", v) 328 b[k] = s[0] 329 } 330 } 331 jwtKey = string(b) 332 log.Info("Please set DOCUMIZESALT or use -salt with this value: " + jwtKey) 333 } 334 return true 335 }) 336 } 337 338 // Generates JSON Web Token (http://jwt.io) 339 func generateJWT(user, org, domain string) string { 340 token := jwt.New(jwt.SigningMethodHS256) 341 342 // issuer 343 token.Claims["iss"] = "Documize" 344 // subject 345 token.Claims["sub"] = "webapp" 346 // expiry 347 token.Claims["exp"] = time.Now().Add(time.Hour * 168).Unix() 348 // data 349 token.Claims["user"] = user 350 token.Claims["org"] = org 351 token.Claims["domain"] = domain 352 353 tokenString, _ := token.SignedString([]byte(jwtKey)) 354 355 return tokenString 356 } 357 358 // Check for authorization token. 359 // We look for 'Authorization' request header OR query string "?token=XXX". 360 func findJWT(r *http.Request) (token string) { 361 header := r.Header.Get("Authorization") 362 363 if header != "" { 364 header = strings.Replace(header, "Bearer ", "", 1) 365 } 366 367 if len(header) > 1 { 368 token = header 369 } else { 370 query := r.URL.Query() 371 token = query.Get("token") 372 } 373 374 if token == "null" { 375 token = "" 376 } 377 378 return 379 } 380 381 // We take in raw token string and decode it. 382 func decodeJWT(tokenString string) (c request.Context, claims map[string]interface{}, err error) { 383 method := "decodeJWT" 384 385 // sensible defaults 386 c.UserID = "" 387 c.OrgID = "" 388 c.Authenticated = false 389 c.Guest = false 390 391 token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 392 return []byte(jwtKey), nil 393 }) 394 395 if err != nil { 396 err = fmt.Errorf("bad authorization token") 397 return 398 } 399 400 if !token.Valid { 401 if ve, ok := err.(*jwt.ValidationError); ok { 402 if ve.Errors&jwt.ValidationErrorMalformed != 0 { 403 log.Error("invalid token", err) 404 err = fmt.Errorf("bad token") 405 return 406 } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { 407 log.Error("expired token", err) 408 err = fmt.Errorf("expired token") 409 return 410 } else { 411 log.Error("invalid token", err) 412 err = fmt.Errorf("bad token") 413 return 414 } 415 } else { 416 log.Error("invalid token", err) 417 err = fmt.Errorf("bad token") 418 return 419 } 420 } 421 422 c = request.NewContext() 423 c.UserID = token.Claims["user"].(string) 424 c.OrgID = token.Claims["org"].(string) 425 426 if len(c.UserID) == 0 || len(c.OrgID) == 0 { 427 err = fmt.Errorf("%s : unable parse token data", method) 428 return 429 } 430 431 c.Authenticated = true 432 c.Guest = false 433 434 return c, token.Claims, nil 435 }