Skip to content

Commit

Permalink
misc: refactor profile api functions
Browse files Browse the repository at this point in the history
  • Loading branch information
greenpau committed Mar 18, 2024
1 parent e9988da commit f2c2b5d
Show file tree
Hide file tree
Showing 19 changed files with 1,141 additions and 503 deletions.
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ clean:
.PHONY: qtest
qtest: covdir
@echo "Perform quick tests ..."
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/tagging/...
@#time richgo test -v -coverprofile=.coverage/coverage.out internal/tag/*.go
@#time richgo test -v -coverprofile=.coverage/coverage.out internal/testutils/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/util/data/...
Expand All @@ -126,15 +127,15 @@ qtest: covdir
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestValidateJwksKey ./pkg/authn/backends/oauth2/jwks*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestTransformData ./pkg/authn/transformer/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/authn/transformer/*.go
@time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/redirects/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/redirects/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/authn/icons/...
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/idp/...
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/idp/saml/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/idp/oauth/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewJwksKeyFromRSAPublicKeyPEM ./pkg/idp/oauth/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewIdentityProviderConfig ./pkg/idp/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/authn/ui/...
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/ids/...
@time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/ids/...
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/ids/local/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/ids/ldap/*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/authz/...
Expand Down
39 changes: 39 additions & 0 deletions internal/tests/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2024 Paul Greenberg [email protected]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tests

import (
"encoding/json"
)

// UnpackDict unpacks interface into a map.
func UnpackDict(i interface{}) (map[string]interface{}, error) {
var m map[string]interface{}
switch v := i.(type) {
case string:
if err := json.Unmarshal([]byte(v), &m); err != nil {
return nil, err
}
default:
b, err := json.Marshal(i)
if err != nil {
return nil, err
}
if err := json.Unmarshal(b, &m); err != nil {
return nil, err
}
}
return m, nil
}
92 changes: 92 additions & 0 deletions internal/tests/data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2022 Paul Greenberg [email protected]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tests

import (
"fmt"

"testing"
)

func TestUnpackDict(t *testing.T) {

testcases := []struct {
name string
input interface{}
want map[string]interface{}
shouldErr bool
err error
disabled bool
}{
{
name: "test unpack json string",
disabled: false,
input: `{
"foo": {
"bar": "baz"
}
}`,
want: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
},
},
},
{
name: "test malformed json string",
disabled: false,
input: `{
{
}`,
shouldErr: true,
err: fmt.Errorf("invalid character '{' looking for beginning of object key string"),
},
{
name: "test unpack map",
disabled: false,
input: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
},
},
want: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
},
},
},
{
name: "test unpack non map",
disabled: false,
input: 123,
shouldErr: true,
err: fmt.Errorf("json: cannot unmarshal number into Go value of type map[string]interface {}"),
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if tc.disabled {
return
}
msgs := []string{fmt.Sprintf("test name: %s", tc.name)}
msgs = append(msgs, fmt.Sprintf("input:\n%v", tc.input))
got, err := UnpackDict(tc.input)
if EvalErrWithLog(t, err, "UnpackDict", tc.shouldErr, tc.err, msgs) {
return
}
EvalObjectsWithLog(t, "UnpackDict", tc.want, got, msgs)
})
}
}
153 changes: 153 additions & 0 deletions pkg/authn/api_add_user_app_multi_factor_authenticator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright 2024 Paul Greenberg [email protected]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package authn

import (
"context"
"encoding/json"
"net/http"

"github.com/greenpau/go-authcrunch/pkg/authn/enums/operator"
"github.com/greenpau/go-authcrunch/pkg/ids"
"github.com/greenpau/go-authcrunch/pkg/requests"
"github.com/greenpau/go-authcrunch/pkg/tagging"
"github.com/greenpau/go-authcrunch/pkg/user"
)

// AddUserAppMultiFactorVerifier adds app multi factor authenticator to user identity.
func (p *Portal) AddUserAppMultiFactorVerifier(
ctx context.Context,
w http.ResponseWriter,
r *http.Request,
rr *requests.Request,
parsedUser *user.User,
resp map[string]interface{},
usr *user.User,
backend ids.IdentityStore,
bodyData map[string]interface{}) error {

var tokenTitle, tokenDescription, tokenSecret string
var tokenLifetime, tokenDigits int
var tokenLabels []string = []string{}
var tokenTags []tagging.Tag = []tagging.Tag{}

// Extract data.
if v, exists := bodyData["period"]; exists {
switch exp := v.(type) {
case float64:
tokenLifetime = int(exp)
case int:
tokenLifetime = exp
case int64:
tokenLifetime = int(exp)
case json.Number:
i, _ := exp.Int64()
tokenLifetime = int(i)
}
} else {
resp["message"] = "Profile API did not find period in the request payload"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}
if v, exists := bodyData["digits"]; exists {
switch exp := v.(type) {
case float64:
tokenDigits = int(exp)
case int:
tokenDigits = exp
case int64:
tokenDigits = int(exp)
case json.Number:
i, _ := exp.Int64()
tokenDigits = int(i)
}
} else {
resp["message"] = "Profile API did not find digits in the request payload"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}
if v, exists := bodyData["title"]; exists {
tokenTitle = v.(string)
} else {
resp["message"] = "Profile API did not find title in the request payload"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}
if v, exists := bodyData["description"]; exists {
tokenDescription = v.(string)
} else {
resp["message"] = "Profile API did not find description in the request payload"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}
if v, exists := bodyData["secret"]; exists {
tokenSecret = v.(string)
} else {
resp["message"] = "Profile API did not find secret in the request payload"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}

if extractedTokenTags, err := tagging.ExtractTags(bodyData); err == nil {
for _, extractedTokenTag := range extractedTokenTags {
tokenTags = append(tokenTags, *extractedTokenTag)
}
} else {
resp["message"] = "Profile API find malformed tags in the request payload"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}

if extractedTokenLabels, err := tagging.ExtractLabels(bodyData); err == nil {
tokenLabels = extractedTokenLabels
} else {
resp["message"] = "Profile API find malformed tags in the request payload"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}

// Validate data.
if !tokenIssuerRegexPattern.MatchString(tokenTitle) {
resp["message"] = "Profile API found non-compliant token title value"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}
if !tokenDescriptionRegexPattern.MatchString(tokenDescription) && (tokenDescription != "") {
resp["message"] = "Profile API found non-compliant token description value"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}
if !tokenSecretRegexPattern.MatchString(tokenSecret) {
resp["message"] = "Profile API found non-compliant token secret value"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}
if tokenLifetime != 15 && tokenLifetime != 30 && tokenLifetime != 60 && tokenLifetime != 90 {
resp["message"] = "Profile API found non-compliant token lifetime value"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}
if tokenDigits != 4 && tokenDigits != 6 && tokenDigits != 8 {
resp["message"] = "Profile API found non-compliant token digits value"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}

rr.MfaToken.SkipVerification = true
rr.MfaToken.Comment = tokenTitle
rr.MfaToken.Description = tokenDescription
rr.MfaToken.Secret = tokenSecret
rr.MfaToken.Type = "totp"
rr.MfaToken.Period = tokenLifetime
rr.MfaToken.Digits = tokenDigits
rr.MfaToken.Labels = tokenLabels
rr.MfaToken.Tags = tokenTags

if err := backend.Request(operator.AddMfaToken, rr); err != nil {
resp["message"] = "Profile API failed to add token identity store"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}

resp["entry"] = "Created"
return handleAPIProfileResponse(w, rr, http.StatusOK, resp)
}
54 changes: 54 additions & 0 deletions pkg/authn/api_delete_user_multi_factor_authenticator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2024 Paul Greenberg [email protected]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package authn

import (
"context"
"net/http"

"github.com/greenpau/go-authcrunch/pkg/authn/enums/operator"
"github.com/greenpau/go-authcrunch/pkg/ids"
"github.com/greenpau/go-authcrunch/pkg/requests"
"github.com/greenpau/go-authcrunch/pkg/user"
)

// DeleteUserMultiFactorVerifier deletes app multi factor authenticator from user identity.
func (p *Portal) DeleteUserMultiFactorVerifier(
ctx context.Context,
w http.ResponseWriter,
r *http.Request,
rr *requests.Request,
parsedUser *user.User,
resp map[string]interface{},
usr *user.User,
backend ids.IdentityStore,
bodyData map[string]interface{}) error {

if v, exists := bodyData["id"]; exists {
rr.MfaToken.ID = v.(string)
} else {
resp["message"] = "Profile API did not find id in the request payload"
return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
}

// Get MFA Token
if err := backend.Request(operator.DeleteMfaToken, rr); err != nil {
resp["message"] = "Profile API failed to delete user multi factor authenticator"
return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp)
}

resp["entry"] = rr.MfaToken.ID
return handleAPIProfileResponse(w, rr, http.StatusOK, resp)
}
Loading

0 comments on commit f2c2b5d

Please sign in to comment.