github.com/greenpau/go-authcrunch@v1.1.4/pkg/user/user_test.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     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  package user
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"github.com/greenpau/go-authcrunch/internal/tests"
    21  	"github.com/greenpau/go-authcrunch/pkg/errors"
    22  	"testing"
    23  	"time"
    24  )
    25  
    26  func TestTokenValidity(t *testing.T) {
    27  	testcases := []struct {
    28  		name      string
    29  		data      []byte
    30  		shouldErr bool
    31  		err       error
    32  	}{
    33  		{
    34  			name: "valid token",
    35  			data: []byte(fmt.Sprintf(`{"exp":%d}`, time.Now().Add(10*time.Minute).Unix())),
    36  		},
    37  		{
    38  			name:      "expired token",
    39  			data:      []byte(fmt.Sprintf(`{"exp":%d}`, time.Now().Add(-10*time.Minute).Unix())),
    40  			shouldErr: true,
    41  			err:       errors.ErrExpiredToken,
    42  		},
    43  	}
    44  	for _, tc := range testcases {
    45  		t.Run(tc.name, func(t *testing.T) {
    46  			var msgs []string
    47  			msgs = append(msgs, fmt.Sprintf("test name: %s", tc.name))
    48  			usr, err := NewUser(tc.data)
    49  			if err == nil {
    50  				msgs = append(msgs, fmt.Sprintf("parsed claims: %v", usr.AsMap()))
    51  				err = usr.Claims.Valid()
    52  			}
    53  			if tests.EvalErrWithLog(t, err, "parse token", tc.shouldErr, tc.err, msgs) {
    54  				return
    55  			}
    56  			wantHeaders := make(map[string]string)
    57  			wantHeaders["foo"] = "bar"
    58  			usr.SetRequestHeaders(wantHeaders)
    59  			gotHeaders := usr.GetRequestHeaders()
    60  			tests.EvalObjectsWithLog(t, "headers", wantHeaders, gotHeaders, msgs)
    61  
    62  			wantIdentity := make(map[string]interface{})
    63  			wantIdentity["foo"] = "bar"
    64  			usr.SetRequestIdentity(wantIdentity)
    65  			gotIdentity := usr.GetRequestIdentity()
    66  			tests.EvalObjectsWithLog(t, "identity", wantIdentity, gotIdentity, msgs)
    67  		})
    68  	}
    69  }
    70  
    71  func TestNewClaimsFromMap(t *testing.T) {
    72  	testcases := []struct {
    73  		name      string
    74  		data      interface{}
    75  		claims    *Claims
    76  		err       error
    77  		shouldErr bool
    78  	}{
    79  		{
    80  			name: "valid claims with metadata mfa claims",
    81  			data: `{"name":"John Smith","metadata":{"mfa_required":true,"mfa_authenticated":false}}`,
    82  			claims: &Claims{
    83  				Name:  "John Smith",
    84  				Roles: []string{"anonymous", "guest"},
    85  				Metadata: map[string]interface{}{
    86  					"mfa_authenticated": false,
    87  					"mfa_required":      true,
    88  				},
    89  			},
    90  		},
    91  		{
    92  			name: "valid claims with email claim",
    93  			data: []byte(`{"email":"jsmith@contoso.com"}`),
    94  			claims: &Claims{
    95  				Email: "jsmith@contoso.com",
    96  				Roles: []string{"anonymous", "guest"},
    97  			},
    98  		},
    99  		{
   100  			name:      "invalid email claim",
   101  			data:      []byte(`{"email": 123456}`),
   102  			shouldErr: true,
   103  			err:       errors.ErrInvalidEmailClaimType.WithArgs("email", 123456.00),
   104  		},
   105  		{
   106  			name:      "malformed json string",
   107  			data:      `{"email": 123456`,
   108  			shouldErr: true,
   109  			err:       fmt.Errorf("unexpected end of JSON input"),
   110  		},
   111  		{
   112  			name:      "user data is nil",
   113  			data:      nil,
   114  			shouldErr: true,
   115  			err:       errors.ErrInvalidUserDataType,
   116  		},
   117  		{
   118  			name: "valid claims with mail claim",
   119  			data: []byte(`{"mail":"jsmith@contoso.com"}`),
   120  			claims: &Claims{
   121  				Email: "jsmith@contoso.com",
   122  				Roles: []string{"anonymous", "guest"},
   123  			},
   124  		},
   125  		{
   126  			name:      "invalid mail claim",
   127  			data:      []byte(`{"mail": 123456}`),
   128  			shouldErr: true,
   129  			err:       errors.ErrInvalidEmailClaimType.WithArgs("mail", 123456.00),
   130  		},
   131  		{
   132  			name: "valid claims with issuer claim",
   133  			data: []byte(`{"iss":"https://127.0.0.1/auth"}`),
   134  			claims: &Claims{
   135  				Issuer: "https://127.0.0.1/auth",
   136  				Roles:  []string{"anonymous", "guest"},
   137  			},
   138  		},
   139  		{
   140  			name:      "invalid issuer claim",
   141  			data:      []byte(`{"iss": 123456}`),
   142  			shouldErr: true,
   143  			err:       errors.ErrInvalidIssuerClaimType.WithArgs(123456.00),
   144  		},
   145  		{
   146  			name: "valid claims with exp, iat, nbf claim in float64",
   147  			data: []byte(`{"exp": 1613327613.00, "nbf": 1613324013.00, "iat": 1613324013.00}`),
   148  			claims: &Claims{
   149  				Roles:     []string{"anonymous", "guest"},
   150  				ExpiresAt: 1613327613,
   151  				IssuedAt:  1613324013,
   152  				NotBefore: 1613324013,
   153  			},
   154  		},
   155  		{
   156  			name: "valid claims with exp, iat, nbf claim in json number",
   157  			data: map[string]interface{}{
   158  				"exp": json.Number("1613327613"),
   159  				"iat": json.Number("1613324013"),
   160  				"nbf": json.Number("1613324013"),
   161  			},
   162  			claims: &Claims{
   163  				Roles:     []string{"anonymous", "guest"},
   164  				ExpiresAt: 1613327613,
   165  				IssuedAt:  1613324013,
   166  				NotBefore: 1613324013,
   167  			},
   168  		},
   169  		{
   170  			name:      "invalid exp claim",
   171  			data:      []byte(`{"exp": "1613327613"}`),
   172  			shouldErr: true,
   173  			err:       errors.ErrInvalidClaimExpiresAt.WithArgs("1613327613"),
   174  		},
   175  		{
   176  			name:      "invalid iat claim",
   177  			data:      []byte(`{"iat": "1613327613"}`),
   178  			shouldErr: true,
   179  			err:       errors.ErrInvalidClaimIssuedAt.WithArgs("1613327613"),
   180  		},
   181  		{
   182  			name:      "invalid nbf claim",
   183  			data:      []byte(`{"nbf": "1613327613"}`),
   184  			shouldErr: true,
   185  			err:       errors.ErrInvalidClaimNotBefore.WithArgs("1613327613"),
   186  		},
   187  		{
   188  			name: "valid jti, sub, aud, origin, addr, and picture claims",
   189  			data: []byte(`{
   190  				"jti": "a9d73486-b647-472a-b380-bea33a6115af",
   191  				"sub":"jsmith",
   192  				"aud":"portal",
   193  				"origin": "localhost",
   194  				"addr": "10.10.10.10",
   195  				"picture": "https://127.0.0.1/avatar.png"
   196  			}`),
   197  			claims: &Claims{
   198  				Audience:   []string{"portal"},
   199  				ID:         "a9d73486-b647-472a-b380-bea33a6115af",
   200  				Subject:    "jsmith",
   201  				Origin:     "localhost",
   202  				Roles:      []string{"anonymous", "guest"},
   203  				Address:    "10.10.10.10",
   204  				PictureURL: "https://127.0.0.1/avatar.png",
   205  			},
   206  		},
   207  		{
   208  			name: "valid aud claim with multiple entries",
   209  			data: []byte(`{"aud":["portal","dashboard"]}`),
   210  			claims: &Claims{
   211  				Audience: []string{"portal", "dashboard"},
   212  				Roles:    []string{"anonymous", "guest"},
   213  			},
   214  		},
   215  		{
   216  			name:      "invalid jti claim",
   217  			data:      []byte(`{"jti": 1613327613}`),
   218  			shouldErr: true,
   219  			err:       errors.ErrInvalidIDClaimType.WithArgs(1613327613.00),
   220  		},
   221  		{
   222  			name:      "invalid sub claim",
   223  			data:      []byte(`{"sub": ["foo", "bar"]}`),
   224  			shouldErr: true,
   225  			err:       errors.ErrInvalidSubjectClaimType.WithArgs([]interface{}{"foo", "bar"}),
   226  		},
   227  		{
   228  			name:      "invalid aud claim with numberic value",
   229  			data:      []byte(`{"aud": 123456}`),
   230  			shouldErr: true,
   231  			err:       errors.ErrInvalidAudienceType.WithArgs(123456.00),
   232  		},
   233  		{
   234  			name:      "invalid aud claim with numberic slice value",
   235  			data:      []byte(`{"aud": [123456]}`),
   236  			shouldErr: true,
   237  			err:       errors.ErrInvalidAudience.WithArgs(123456.00),
   238  		},
   239  		{
   240  			name:      "invalid origin claim",
   241  			data:      []byte(`{"origin": 123456}`),
   242  			shouldErr: true,
   243  			err:       errors.ErrInvalidOriginClaimType.WithArgs(123456.00),
   244  		},
   245  		{
   246  			name: "valid roles claim",
   247  			data: []byte(`{"roles": "admin editor"}`),
   248  			claims: &Claims{
   249  				Roles: []string{"admin", "editor"},
   250  			},
   251  		},
   252  		{
   253  			name: "valid groups claim",
   254  			data: []byte(`{"groups":["admin","editor"]}`),
   255  			claims: &Claims{
   256  				Roles: []string{"admin", "editor"},
   257  			},
   258  		},
   259  		{
   260  			name:      "invalid roles claim",
   261  			data:      []byte(`{"roles": 123456}`),
   262  			shouldErr: true,
   263  			err:       errors.ErrInvalidRoleType.WithArgs(123456.00),
   264  		},
   265  		{
   266  			name:      "invalid groups claim",
   267  			data:      []byte(`{"roles":[123456, 234567]}`),
   268  			shouldErr: true,
   269  			err:       errors.ErrInvalidRole.WithArgs(234567.00),
   270  		},
   271  		{
   272  			name: "valid name claim with slice",
   273  			data: []byte(`{"name":["jsmith@contoso.com", "John Smith"]}`),
   274  			claims: &Claims{
   275  				Name:  "jsmith@contoso.com John Smith",
   276  				Roles: []string{"anonymous", "guest"},
   277  			},
   278  		},
   279  		{
   280  			name: "valid name claim with slice and email claim with the email from name claim",
   281  			data: []byte(`{"name":["jsmith@contoso.com", "John Smith"],"mail":"jsmith@contoso.com"}`),
   282  			claims: &Claims{
   283  				Name:  "John Smith",
   284  				Email: "jsmith@contoso.com",
   285  				Roles: []string{"anonymous", "guest"},
   286  			},
   287  		},
   288  		{
   289  			name:      "invalid name claim with numeric slice",
   290  			data:      []byte(`{"name":[123456, 234567]}`),
   291  			shouldErr: true,
   292  			err:       errors.ErrInvalidNameClaimType.WithArgs([]interface{}{123456, 234567}),
   293  		},
   294  		{
   295  			name:      "invalid name claim with numeric value",
   296  			data:      []byte(`{"name": 234567}`),
   297  			shouldErr: true,
   298  			err:       errors.ErrInvalidNameClaimType.WithArgs(234567.00),
   299  		},
   300  		{
   301  			name:      "invalid addr claim",
   302  			data:      []byte(`{"addr": 234567}`),
   303  			shouldErr: true,
   304  			err:       errors.ErrInvalidAddrType.WithArgs(234567.00),
   305  		},
   306  		{
   307  			name:      "invalid picture claim",
   308  			data:      []byte(`{"picture": 234567}`),
   309  			shouldErr: true,
   310  			err:       errors.ErrInvalidPictureClaimType.WithArgs(234567.00),
   311  		},
   312  		{
   313  			name:      "invalid metadata claim",
   314  			data:      []byte(`{"metadata": 234567}`),
   315  			shouldErr: true,
   316  			err:       errors.ErrInvalidMetadataClaimType.WithArgs(234567.00),
   317  		},
   318  
   319  		{
   320  			name: "valid app_metadata claim",
   321  			data: []byte(`{"app_metadata":{"authorization":{"roles":["admin", "editor"]}}}`),
   322  			claims: &Claims{
   323  				Roles: []string{"admin", "editor"},
   324  			},
   325  		},
   326  		{
   327  			name:      "invalid app_metadata claim with numeric roles slice",
   328  			data:      []byte(`{"app_metadata":{"authorization":{"roles":[123456, 234567]}}}`),
   329  			shouldErr: true,
   330  			err:       errors.ErrInvalidRole.WithArgs(123456.00),
   331  		},
   332  		{
   333  			name:      "invalid app_metadata claim with numeric roles value",
   334  			data:      []byte(`{"app_metadata":{"authorization":{"roles": 123456}}}`),
   335  			shouldErr: true,
   336  			err:       errors.ErrInvalidAppMetadataRoleType.WithArgs(123456.00),
   337  		},
   338  		{
   339  			name: "valid realm_access claim",
   340  			data: []byte(`{"realm_access":{"roles":["admin", "editor"]}}`),
   341  			claims: &Claims{
   342  				Roles: []string{"admin", "editor"},
   343  			},
   344  		},
   345  		{
   346  			name:      "invalid realm_access claim with numeric roles slice",
   347  			data:      []byte(`{"realm_access":{"roles":[123456, 234567]}}`),
   348  			shouldErr: true,
   349  			err:       errors.ErrInvalidRole.WithArgs(123456.00),
   350  		},
   351  		{
   352  			name: "valid acl claim with paths map",
   353  			data: []byte(`{"acl":{"paths":{"/*/users/**": {}, "/*/conversations/**": {}}}}`),
   354  			claims: &Claims{
   355  				Roles: []string{"anonymous", "guest"},
   356  				AccessList: &AccessListClaim{
   357  					Paths: map[string]interface{}{
   358  						"/*/conversations/**": map[string]interface{}{},
   359  						"/*/users/**":         map[string]interface{}{},
   360  					},
   361  				},
   362  			},
   363  		},
   364  		{
   365  			name: "valid acl claim with paths slice",
   366  			data: []byte(`{"acl":{"paths":["/*/users/**", "/*/conversations/**"]}}`),
   367  			claims: &Claims{
   368  				Roles: []string{"anonymous", "guest"},
   369  				AccessList: &AccessListClaim{
   370  					Paths: map[string]interface{}{
   371  						"/*/conversations/**": map[string]interface{}{},
   372  						"/*/users/**":         map[string]interface{}{},
   373  					},
   374  				},
   375  			},
   376  		},
   377  		{
   378  			name:      "invalid acl claim with numeric paths slice",
   379  			data:      []byte(`{"acl":{"paths":["/*/users/**", 123456]}}`),
   380  			shouldErr: true,
   381  			err:       errors.ErrInvalidAccessListPath.WithArgs(123456.00),
   382  		},
   383  		{
   384  			name: "valid scopes claim with string slice",
   385  			data: []byte(`{"scopes": ["repo", "public_repo"]}`),
   386  			claims: &Claims{
   387  				Roles:  []string{"anonymous", "guest"},
   388  				Scopes: []string{"repo", "public_repo"},
   389  			},
   390  		},
   391  		{
   392  			name: "valid scopes claim string value",
   393  			data: []byte(`{"scopes": "repo public_repo"}`),
   394  			claims: &Claims{
   395  				Roles:  []string{"anonymous", "guest"},
   396  				Scopes: []string{"repo", "public_repo"},
   397  			},
   398  		},
   399  		{
   400  			name:      "invalid scopes claim with numeric slice",
   401  			data:      []byte(`{"scopes": [123456]}`),
   402  			shouldErr: true,
   403  			err:       errors.ErrInvalidScope.WithArgs(123456.00),
   404  		},
   405  		{
   406  			name:      "invalid scopes claim with numeric value",
   407  			data:      []byte(`{"scopes": 123456}`),
   408  			shouldErr: true,
   409  			err:       errors.ErrInvalidScopeType.WithArgs(123456.00),
   410  		},
   411  
   412  		{
   413  			name: "valid paths claim",
   414  			data: []byte(`{"paths": ["/dropbox/jsmith/README.md"]}`),
   415  			claims: &Claims{
   416  				Roles: []string{"anonymous", "guest"},
   417  				AccessList: &AccessListClaim{
   418  					Paths: map[string]interface{}{"/dropbox/jsmith/README.md": map[string]interface{}{}},
   419  				},
   420  			},
   421  		},
   422  		{
   423  			name:      "invalid paths claim with numeric slice",
   424  			data:      []byte(`{"paths": [123456]}`),
   425  			shouldErr: true,
   426  			err:       errors.ErrInvalidAccessListPath.WithArgs(123456.00),
   427  		},
   428  
   429  		{
   430  			name: "valid org claim with string",
   431  			data: []byte(`{"org": "foo bar"}`),
   432  			claims: &Claims{
   433  				Organizations: []string{"foo", "bar"},
   434  				Roles:         []string{"anonymous", "guest"},
   435  			},
   436  		},
   437  		{
   438  			name: "valid org claim with slice",
   439  			data: []byte(`{"org": ["foo","bar"]}`),
   440  			claims: &Claims{
   441  				Organizations: []string{"foo", "bar"},
   442  				Roles:         []string{"anonymous", "guest"},
   443  			},
   444  		},
   445  		{
   446  			name:      "invalid org claim with numeric value",
   447  			data:      []byte(`{"org": 123456}`),
   448  			shouldErr: true,
   449  			err:       errors.ErrInvalidOrgType.WithArgs(123456.00),
   450  		},
   451  		{
   452  			name:      "invalid org claim with numeric slice",
   453  			data:      []byte(`{"org":[123456, 234567]}`),
   454  			shouldErr: true,
   455  			err:       errors.ErrInvalidOrg.WithArgs(234567.00),
   456  		},
   457  		{
   458  			name:      "invalid json map",
   459  			data:      []byte(`{"org":`),
   460  			shouldErr: true,
   461  			err:       fmt.Errorf("unexpected end of JSON input"),
   462  		},
   463  	}
   464  	for _, tc := range testcases {
   465  		t.Run(tc.name, func(t *testing.T) {
   466  			var msgs []string
   467  			msgs = append(msgs, fmt.Sprintf("test name: %s", tc.name))
   468  			usr, err := NewUser(tc.data)
   469  			if tests.EvalErrWithLog(t, err, "user map", tc.shouldErr, tc.err, msgs) {
   470  				return
   471  			}
   472  			msgs = append(msgs, fmt.Sprintf("parsed claims: %v", usr.AsMap()))
   473  			msgs = append(msgs, fmt.Sprintf("extracted key-values: %v", usr.GetData()))
   474  			tests.CustomEvalObjectsWithLog(t, "response", tc.claims, usr.Claims, msgs, Claims{})
   475  		})
   476  	}
   477  }