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  }