Skip to content
Draft
67 changes: 67 additions & 0 deletions internal/apptoken/apptoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package apptoken

import (
"time"

"github.com/hashicorp/boundary/internal/db/timestamp"
)

// An AppToken is an application token used for machine-to-machine authentication.
type AppToken struct {
PublicId string
ScopeId string
Name string
Description string
CreateTime *timestamp.Timestamp
ApproximateLastAccessTime *timestamp.Timestamp
ExpirationTime *timestamp.Timestamp
TimeToStaleSeconds uint32
Token string // Token is a plaintext value of the token
CreatedByUserId string
KeyId string
Revoked bool
Permissions []AppTokenPermission
}

// AppTokenPermission represents the permissions granted to an AppToken.
// The individual scopes granted to an AppTokenPermission will remain constant over time.
// When a scope is removed, it is moved from GrantedScopes to DeletedScopes.
// The union of GrantedScopes and DeletedScopes will always equal the original set of granted scopes.
type AppTokenPermission struct {
Label string
Grants []string
GrantedScopes []string
DeletedScopes []DeletedScope
}

// DeletedScope represents a scope which has been deleted from an AppTokenPermission.
type DeletedScope struct {
ScopeId string
TimeStamp *timestamp.Timestamp
}

// Methods

// IsActive returns true if the app token is active (not revoked and not expired)
// An AppToken is considered inactive if:
// - Token is revoked
// - time.Now() is after expiration time
// - time.Now() is after lastAccess + timeToStaleSeconds
func (a *AppToken) IsActive() bool {
now := time.Now()

switch {
case a.Revoked:
return false
case a.ExpirationTime != nil && now.After(a.ExpirationTime.AsTime()):
return false
case a.TimeToStaleSeconds > 0 && a.ApproximateLastAccessTime != nil &&
now.After(a.ApproximateLastAccessTime.AsTime().Add(time.Duration(a.TimeToStaleSeconds)*time.Second)):
return false
default:
return true
}
}
81 changes: 81 additions & 0 deletions internal/apptoken/apptoken_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package apptoken

import (
"testing"
"time"

"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/stretchr/testify/assert"
)

func TestAppToken_IsActive(t *testing.T) {
now := timestamp.Now()
past := timestamp.New(now.AsTime().Add(-1 * time.Hour))
future := timestamp.New(now.AsTime().Add(1 * time.Hour))

tests := []struct {
name string
token *AppToken
want bool
}{
{
name: "active token",
token: &AppToken{
Revoked: false,
ExpirationTime: future,
ApproximateLastAccessTime: now,
TimeToStaleSeconds: 3600,
},
want: true,
},
{
name: "revoked token",
token: &AppToken{
Revoked: true,
ExpirationTime: future,
ApproximateLastAccessTime: now,
TimeToStaleSeconds: 3600,
},
want: false,
},
{
name: "expired token",
token: &AppToken{
Revoked: false,
ExpirationTime: past,
ApproximateLastAccessTime: now,
TimeToStaleSeconds: 3600,
},
want: false,
},
{
name: "stale token",
token: &AppToken{
Revoked: false,
ExpirationTime: future,
ApproximateLastAccessTime: past,
TimeToStaleSeconds: 1800,
},
want: false,
},
{
name: "no TimeToStaleSeconds set",
token: &AppToken{
Revoked: false,
ExpirationTime: future,
ApproximateLastAccessTime: past,
TimeToStaleSeconds: 0,
},
want: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, tt.token.IsActive())
})
}
}
34 changes: 34 additions & 0 deletions internal/apptoken/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package apptoken

// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
for _, o := range opt {
if o != nil {
o(&opts)
}
}
return opts
}

// Option - how Options are passed as arguments
type Option func(*options)

func getDefaultOptions() options {
return options{}
}

// options = how options are represented
type options struct {
withRecursive bool
}

// WithRecursive indicates that this request is a recursive request
func WithRecursive(isRecursive bool) Option {
return func(o *options) {
o.withRecursive = isRecursive
}
}
Loading
Loading