github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/mw_hmac_test.go (about)

     1  package gateway
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"crypto/sha1"
     6  	"crypto/sha512"
     7  	"encoding/base64"
     8  	"fmt"
     9  	"hash"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/url"
    13  	"regexp"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/justinas/alice"
    20  	"github.com/lonelycode/go-uuid/uuid"
    21  
    22  	"github.com/TykTechnologies/tyk/apidef"
    23  	"github.com/TykTechnologies/tyk/config"
    24  	"github.com/TykTechnologies/tyk/user"
    25  )
    26  
    27  const hmacAuthDef = `{
    28  	"api_id": "1",
    29  	"org_id": "default",
    30  	"enable_signature_checking": true,
    31  	"hmac_allowed_clock_skew": 5000,
    32  	"auth": {"auth_header_name": "authorization"},
    33  	"version_data": {
    34  		"not_versioned": true,
    35  		"versions": {
    36  			"v1": {"name": "v1"}
    37  		}
    38  	},
    39  	"proxy": {
    40  		"listen_path": "/v1",
    41  		"target_url": "` + testHttpAny + `"
    42  	}
    43  }`
    44  
    45  func createHMACAuthSession() *user.SessionState {
    46  	session := new(user.SessionState)
    47  	session.Rate = 8.0
    48  	session.Allowance = session.Rate
    49  	session.LastCheck = time.Now().Unix()
    50  	session.Per = 1.0
    51  	session.QuotaRenewalRate = 300 // 5 minutes
    52  	session.QuotaRenews = time.Now().Unix() + 20
    53  	session.QuotaRemaining = 1
    54  	session.QuotaMax = -1
    55  	session.HMACEnabled = true
    56  	session.HmacSecret = "9879879878787878"
    57  	return session
    58  }
    59  
    60  func getHMACAuthChain(spec *APISpec) http.Handler {
    61  	remote, _ := url.Parse(testHttpAny)
    62  	proxy := TykNewSingleHostReverseProxy(remote, spec)
    63  	proxyHandler := ProxyHandler(proxy, spec)
    64  	baseMid := BaseMiddleware{Spec: spec, Proxy: proxy}
    65  	chain := alice.New(mwList(
    66  		&IPWhiteListMiddleware{baseMid},
    67  		&IPBlackListMiddleware{BaseMiddleware: baseMid},
    68  		&HMACMiddleware{BaseMiddleware: baseMid},
    69  		&VersionCheck{BaseMiddleware: baseMid},
    70  		&KeyExpired{baseMid},
    71  		&AccessRightsCheck{baseMid},
    72  		&RateLimitAndQuotaCheck{baseMid},
    73  	)...).Then(proxyHandler)
    74  	return chain
    75  }
    76  
    77  type testAuthFailEventHandler struct {
    78  	cb func(config.EventMessage)
    79  }
    80  
    81  func (w *testAuthFailEventHandler) Init(handlerConf interface{}) error {
    82  	return nil
    83  }
    84  
    85  func (w *testAuthFailEventHandler) HandleEvent(em config.EventMessage) {
    86  	w.cb(em)
    87  }
    88  
    89  func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
    90  	c := make(chan struct{})
    91  	go func() {
    92  		defer close(c)
    93  		wg.Wait()
    94  	}()
    95  	select {
    96  	case <-c:
    97  		return false // completed normally
    98  	case <-time.After(timeout):
    99  		return true // timed out
   100  	}
   101  }
   102  
   103  func testPrepareHMACAuthSessionPass(tb testing.TB, hashFn func() hash.Hash, eventWG *sync.WaitGroup, withHeader bool, isBench bool) (string, *APISpec, *http.Request, string) {
   104  	spec := CreateSpecTest(tb, hmacAuthDef)
   105  	session := createHMACAuthSession()
   106  
   107  	// Should not receive an AuthFailure event
   108  	cb := func(em config.EventMessage) {
   109  		eventWG.Done()
   110  	}
   111  	spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{
   112  		"AuthFailure": {&testAuthFailEventHandler{cb}},
   113  	}
   114  
   115  	sessionKey := ""
   116  	if isBench {
   117  		sessionKey = uuid.New()
   118  	} else {
   119  		sessionKey = "9876"
   120  	}
   121  
   122  	spec.SessionManager.UpdateSession(sessionKey, session, 60, false)
   123  
   124  	req := TestReq(tb, "GET", "/", nil)
   125  
   126  	refDate := "Mon, 02 Jan 2006 15:04:05 MST"
   127  
   128  	// Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))"
   129  
   130  	// Prep the signature string
   131  	tim := time.Now().Format(refDate)
   132  	req.Header.Set("Date", tim)
   133  	signatureString := ""
   134  	if withHeader {
   135  		req.Header.Set("X-Test-1", "hello")
   136  		req.Header.Set("X-Test-2", "world")
   137  		signatureString = strings.ToLower("(request-target): ") + "get /\n"
   138  		signatureString += strings.ToLower("Date") + ": " + tim + "\n"
   139  		signatureString += strings.ToLower("X-Test-1") + ": " + "hello" + "\n"
   140  		signatureString += strings.ToLower("X-Test-2") + ": " + "world"
   141  	} else {
   142  		signatureString = strings.ToLower("Date") + ": " + tim
   143  	}
   144  
   145  	// Encode it
   146  	key := []byte(session.HmacSecret)
   147  	h := hmac.New(hashFn, key)
   148  	h.Write([]byte(signatureString))
   149  
   150  	sigString := base64.StdEncoding.EncodeToString(h.Sum(nil))
   151  	encodedString := url.QueryEscape(sigString)
   152  
   153  	return encodedString, spec, req, sessionKey
   154  }
   155  
   156  func TestHMACAuthSessionPass(t *testing.T) {
   157  	// Should not receive an AuthFailure event
   158  	var eventWG sync.WaitGroup
   159  	eventWG.Add(1)
   160  	encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(t, sha1.New, &eventWG, false, false)
   161  
   162  	recorder := httptest.NewRecorder()
   163  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha1\",signature=\"%s\"", sessionKey, encodedString))
   164  
   165  	chain := getHMACAuthChain(spec)
   166  	chain.ServeHTTP(recorder, req)
   167  
   168  	if recorder.Code != 200 {
   169  		t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code, recorder.Body.String())
   170  	}
   171  
   172  	// Check we did not get our AuthFailure event
   173  	if !waitTimeout(&eventWG, 20*time.Millisecond) {
   174  		t.Error("Request should not have generated an AuthFailure event!: \n")
   175  	}
   176  }
   177  
   178  func TestHMACAuthSessionSHA512Pass(t *testing.T) {
   179  	// Should not receive an AuthFailure event
   180  	var eventWG sync.WaitGroup
   181  	eventWG.Add(1)
   182  	encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(t, sha512.New, &eventWG, false, false)
   183  
   184  	recorder := httptest.NewRecorder()
   185  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha512\",signature=\"%s\"", sessionKey, encodedString))
   186  
   187  	spec.HmacAllowedAlgorithms = []string{"hmac-sha512"}
   188  	chain := getHMACAuthChain(spec)
   189  	chain.ServeHTTP(recorder, req)
   190  
   191  	if recorder.Code != 200 {
   192  		t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code, recorder.Body.String())
   193  	}
   194  
   195  	// Check we did not get our AuthFailure event
   196  	if !waitTimeout(&eventWG, 20*time.Millisecond) {
   197  		t.Error("Request should not have generated an AuthFailure event!: \n")
   198  	}
   199  }
   200  
   201  func BenchmarkHMACAuthSessionPass(b *testing.B) {
   202  	b.ReportAllocs()
   203  
   204  	var eventWG sync.WaitGroup
   205  	eventWG.Add(b.N)
   206  	encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(b, sha1.New, &eventWG, false, true)
   207  
   208  	recorder := httptest.NewRecorder()
   209  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha1\",signature=\"%s\"", sessionKey, encodedString))
   210  
   211  	chain := getHMACAuthChain(spec)
   212  
   213  	for i := 0; i < b.N; i++ {
   214  		chain.ServeHTTP(recorder, req)
   215  		if recorder.Code != 200 {
   216  			b.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code, recorder.Body.String())
   217  		}
   218  	}
   219  }
   220  
   221  func TestHMACAuthSessionAuxDateHeader(t *testing.T) {
   222  	spec := CreateSpecTest(t, hmacAuthDef)
   223  	session := createHMACAuthSession()
   224  
   225  	// Should not receive an AuthFailure event
   226  	var eventWG sync.WaitGroup
   227  	eventWG.Add(1)
   228  	cb := func(em config.EventMessage) {
   229  		eventWG.Done()
   230  	}
   231  	spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{
   232  		"AuthFailure": {&testAuthFailEventHandler{cb}},
   233  	}
   234  
   235  	// Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session.
   236  	spec.SessionManager.UpdateSession("9876", session, 60, false)
   237  
   238  	recorder := httptest.NewRecorder()
   239  	req := TestReq(t, "GET", "/", nil)
   240  
   241  	refDate := "Mon, 02 Jan 2006 15:04:05 MST"
   242  
   243  	// Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))"
   244  
   245  	// Prep the signature string
   246  	tim := time.Now().Format(refDate)
   247  	req.Header.Set("x-aux-date", tim)
   248  	signatureString := strings.ToLower("x-aux-date") + ": " + tim
   249  
   250  	// Encode it
   251  	key := []byte(session.HmacSecret)
   252  	h := hmac.New(sha1.New, key)
   253  	h.Write([]byte(signatureString))
   254  
   255  	sigString := base64.StdEncoding.EncodeToString(h.Sum(nil))
   256  	encodedString := url.QueryEscape(sigString)
   257  
   258  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"9876\",algorithm=\"hmac-sha1\",signature=\"%s\"", encodedString))
   259  
   260  	chain := getHMACAuthChain(spec)
   261  	chain.ServeHTTP(recorder, req)
   262  
   263  	if recorder.Code != 200 {
   264  		t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code)
   265  	}
   266  
   267  	// Check we did not get our AuthFailure event
   268  	if !waitTimeout(&eventWG, 20*time.Millisecond) {
   269  		t.Error("Request should not have generated an AuthFailure event!: \n")
   270  	}
   271  }
   272  
   273  func TestHMACAuthSessionFailureDateExpired(t *testing.T) {
   274  	spec := CreateSpecTest(t, hmacAuthDef)
   275  	session := createHMACAuthSession()
   276  
   277  	// Should receive an AuthFailure event
   278  	var eventWG sync.WaitGroup
   279  	eventWG.Add(1)
   280  	cb := func(em config.EventMessage) {
   281  		eventWG.Done()
   282  	}
   283  	spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{
   284  		"AuthFailure": {&testAuthFailEventHandler{cb}},
   285  	}
   286  
   287  	// Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session.
   288  	spec.SessionManager.UpdateSession("9876", session, 60, false)
   289  
   290  	recorder := httptest.NewRecorder()
   291  	req := TestReq(t, "GET", "/", nil)
   292  
   293  	refDate := "Mon, 02 Jan 2006 15:04:05 MST"
   294  
   295  	// Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))"
   296  
   297  	// Prep the signature string
   298  	tim := time.Now().Format(refDate)
   299  	req.Header.Set("Date", tim)
   300  	signatureString := strings.ToLower("Date") + ":" + tim
   301  
   302  	// Encode it
   303  	key := []byte(session.HmacSecret)
   304  	h := hmac.New(sha1.New, key)
   305  	h.Write([]byte(signatureString))
   306  
   307  	sigString := base64.StdEncoding.EncodeToString(h.Sum(nil))
   308  	encodedString := url.QueryEscape(sigString)
   309  
   310  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"9876\",algorithm=\"hmac-sha1\",signature=\"%s\"", encodedString))
   311  
   312  	chain := getHMACAuthChain(spec)
   313  	chain.ServeHTTP(recorder, req)
   314  
   315  	if recorder.Code != 400 {
   316  		t.Error("Request should have failed with out of date error!: \n", recorder.Code)
   317  	}
   318  
   319  	// Check we did get our AuthFailure event
   320  	if waitTimeout(&eventWG, 20*time.Millisecond) {
   321  		t.Error("Request should have generated an AuthFailure event!: \n")
   322  	}
   323  }
   324  
   325  func TestHMACAuthSessionKeyMissing(t *testing.T) {
   326  	spec := CreateSpecTest(t, hmacAuthDef)
   327  	session := createHMACAuthSession()
   328  
   329  	// Should receive an AuthFailure event
   330  	var eventWG sync.WaitGroup
   331  	eventWG.Add(1)
   332  	cb := func(em config.EventMessage) {
   333  		eventWG.Done()
   334  	}
   335  	spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{
   336  		"AuthFailure": {&testAuthFailEventHandler{cb}},
   337  	}
   338  
   339  	// Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session.
   340  	spec.SessionManager.UpdateSession("9876", session, 60, false)
   341  
   342  	recorder := httptest.NewRecorder()
   343  	req := TestReq(t, "GET", "/", nil)
   344  
   345  	refDate := "Mon, 02 Jan 2006 15:04:05 MST"
   346  
   347  	// Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))"
   348  
   349  	// Prep the signature string
   350  	tim := time.Now().Format(refDate)
   351  	req.Header.Set("Date", tim)
   352  	signatureString := strings.ToLower("Date") + ":" + tim
   353  
   354  	// Encode it
   355  	key := []byte(session.HmacSecret)
   356  	h := hmac.New(sha1.New, key)
   357  	h.Write([]byte(signatureString))
   358  
   359  	sigString := base64.StdEncoding.EncodeToString(h.Sum(nil))
   360  	encodedString := url.QueryEscape(sigString)
   361  
   362  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"98765\",algorithm=\"hmac-sha1\",signature=\"%s\"", encodedString))
   363  
   364  	chain := getHMACAuthChain(spec)
   365  	chain.ServeHTTP(recorder, req)
   366  
   367  	if recorder.Code != 400 {
   368  		t.Error("Request should have failed with key not found error!: \n", recorder.Code)
   369  	}
   370  
   371  	// Check we did get our AuthFailure event
   372  	if waitTimeout(&eventWG, 20*time.Millisecond) {
   373  		t.Error("Request should have generated an AuthFailure event!: \n")
   374  	}
   375  }
   376  
   377  func TestHMACAuthSessionMalformedHeader(t *testing.T) {
   378  	spec := CreateSpecTest(t, hmacAuthDef)
   379  	session := createHMACAuthSession()
   380  
   381  	// Should receive an AuthFailure event
   382  	var eventWG sync.WaitGroup
   383  	eventWG.Add(1)
   384  	cb := func(em config.EventMessage) {
   385  		eventWG.Done()
   386  	}
   387  	spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{
   388  		"AuthFailure": {&testAuthFailEventHandler{cb}},
   389  	}
   390  
   391  	// Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session.
   392  	spec.SessionManager.UpdateSession("9876", session, 60, false)
   393  
   394  	recorder := httptest.NewRecorder()
   395  	req := TestReq(t, "GET", "/", nil)
   396  
   397  	refDate := "Mon, 02 Jan 2006 15:04:05 MST"
   398  
   399  	// Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))"
   400  
   401  	// Prep the signature string
   402  	tim := time.Now().Format(refDate)
   403  	req.Header.Set("Date", tim)
   404  	signatureString := strings.ToLower("Date") + ":" + tim
   405  
   406  	// Encode it
   407  	key := []byte(session.HmacSecret)
   408  	h := hmac.New(sha1.New, key)
   409  	h.Write([]byte(signatureString))
   410  
   411  	sigString := base64.StdEncoding.EncodeToString(h.Sum(nil))
   412  	encodedString := url.QueryEscape(sigString)
   413  
   414  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyID=\"98765\", algorithm=\"hmac-sha256\", signature=\"%s\"", encodedString))
   415  
   416  	chain := getHMACAuthChain(spec)
   417  	chain.ServeHTTP(recorder, req)
   418  
   419  	if recorder.Code != 400 {
   420  		t.Error("Request should have failed with key not found error!: \n", recorder.Code)
   421  	}
   422  
   423  	// Check we did get our AuthFailure event
   424  	if waitTimeout(&eventWG, 20*time.Millisecond) {
   425  		t.Error("Request should have generated an AuthFailure event!: \n")
   426  	}
   427  }
   428  
   429  func TestHMACAuthSessionPassWithHeaderField(t *testing.T) {
   430  	// Should not receive an AuthFailure event
   431  	var eventWG sync.WaitGroup
   432  	eventWG.Add(1)
   433  	encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(t, sha1.New, &eventWG, true, false)
   434  
   435  	recorder := httptest.NewRecorder()
   436  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha1\",headers=\"(request-target) date x-test-1 x-test-2\",signature=\"%s\"", sessionKey, encodedString))
   437  
   438  	chain := getHMACAuthChain(spec)
   439  	chain.ServeHTTP(recorder, req)
   440  
   441  	if recorder.Code != 200 {
   442  		t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code)
   443  	}
   444  
   445  	// Check we did not get our AuthFailure event
   446  	if !waitTimeout(&eventWG, 20*time.Millisecond) {
   447  		t.Error("Request should not have generated an AuthFailure event!: \n")
   448  	}
   449  }
   450  
   451  func BenchmarkHMACAuthSessionPassWithHeaderField(b *testing.B) {
   452  	b.ReportAllocs()
   453  
   454  	var eventWG sync.WaitGroup
   455  	eventWG.Add(b.N)
   456  	encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(b, sha1.New, &eventWG, true, true)
   457  
   458  	recorder := httptest.NewRecorder()
   459  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha1\",headers=\"(request-target) date x-test-1 x-test-2\",signature=\"%s\"", sessionKey, encodedString))
   460  
   461  	chain := getHMACAuthChain(spec)
   462  
   463  	for i := 0; i < b.N; i++ {
   464  		chain.ServeHTTP(recorder, req)
   465  		if recorder.Code != 200 {
   466  			b.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code)
   467  		}
   468  	}
   469  }
   470  
   471  func getUpperCaseEscaped(signature string) []string {
   472  	r := regexp.MustCompile(`%[A-F0-9][A-F0-9]`)
   473  	foundList := r.FindAllString(signature, -1)
   474  	return foundList
   475  }
   476  
   477  func replaceUpperCase(originalSignature string, lowercaseList []string) string {
   478  	newSignature := originalSignature
   479  	for _, lStr := range lowercaseList {
   480  		asUpper := strings.ToLower(lStr)
   481  		newSignature = strings.Replace(newSignature, lStr, asUpper, -1)
   482  	}
   483  
   484  	return newSignature
   485  }
   486  
   487  func TestHMACAuthSessionPassWithHeaderFieldLowerCase(t *testing.T) {
   488  	spec := CreateSpecTest(t, hmacAuthDef)
   489  	session := createHMACAuthSession()
   490  
   491  	// Should not receive an AuthFailure event
   492  	var eventWG sync.WaitGroup
   493  	eventWG.Add(1)
   494  	cb := func(em config.EventMessage) {
   495  		eventWG.Done()
   496  	}
   497  	spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{
   498  		"AuthFailure": {&testAuthFailEventHandler{cb}},
   499  	}
   500  
   501  	// Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session.
   502  	spec.SessionManager.UpdateSession("9876", session, 60, false)
   503  
   504  	recorder := httptest.NewRecorder()
   505  	req := TestReq(t, "GET", "/", nil)
   506  
   507  	refDate := "Mon, 02 Jan 2006 15:04:05 MST"
   508  
   509  	// Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))"
   510  
   511  	// Prep the signature string
   512  	tim := time.Now().Format(refDate)
   513  	req.Header.Set("Date", tim)
   514  	req.Header.Set("X-Test-1", "hello?")
   515  	req.Header.Set("X-Test-2", "world£")
   516  	signatureString := strings.ToLower("(request-target): ") + "get /\n"
   517  	signatureString += strings.ToLower("Date") + ": " + tim + "\n"
   518  	signatureString += strings.ToLower("X-Test-1") + ": " + "hello?" + "\n"
   519  	signatureString += strings.ToLower("X-Test-2") + ": " + "world£"
   520  
   521  	// Encode it
   522  	key := []byte(session.HmacSecret)
   523  	h := hmac.New(sha1.New, key)
   524  	h.Write([]byte(signatureString))
   525  
   526  	sigString := base64.StdEncoding.EncodeToString(h.Sum(nil))
   527  	encodedString := url.QueryEscape(sigString)
   528  
   529  	upperCaseList := getUpperCaseEscaped(encodedString)
   530  	newEncodedSignature := replaceUpperCase(encodedString, upperCaseList)
   531  
   532  	req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"9876\",algorithm=\"hmac-sha1\",headers=\"(request-target) date x-test-1 x-test-2\",signature=\"%s\"", newEncodedSignature))
   533  
   534  	chain := getHMACAuthChain(spec)
   535  	chain.ServeHTTP(recorder, req)
   536  
   537  	if recorder.Code != 200 {
   538  		t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code)
   539  	}
   540  
   541  	// Check we did not get our AuthFailure event
   542  	if !waitTimeout(&eventWG, 20*time.Millisecond) {
   543  		t.Error("Request should not have generated an AuthFailure event!: \n")
   544  	}
   545  }