mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-11 06:33:49 +02:00
bump backend go deps
This commit is contained in:
+2
-2
@@ -24,7 +24,7 @@ require (
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/mod v0.24.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/net v0.38.0
|
||||
golang.org/x/time v0.3.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
@@ -50,7 +50,7 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
|
||||
+4
-4
@@ -63,8 +63,8 @@ github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4
|
||||
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -171,8 +171,8 @@ golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZ
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
Generated
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package cache allows third parties to implement external storage for caching token data
|
||||
for distributed systems or multiple local applications access.
|
||||
|
||||
The data stored and extracted will represent the entire cache. Therefore it is recommended
|
||||
one msal instance per user. This data is considered opaque and there are no guarantees to
|
||||
implementers on the format being passed.
|
||||
*/
|
||||
package cache
|
||||
|
||||
import "context"
|
||||
|
||||
// Marshaler marshals data from an internal cache to bytes that can be stored.
|
||||
type Marshaler interface {
|
||||
Marshal() ([]byte, error)
|
||||
}
|
||||
|
||||
// Unmarshaler unmarshals data from a storage medium into the internal cache, overwriting it.
|
||||
type Unmarshaler interface {
|
||||
Unmarshal([]byte) error
|
||||
}
|
||||
|
||||
// Serializer can serialize the cache to binary or from binary into the cache.
|
||||
type Serializer interface {
|
||||
Marshaler
|
||||
Unmarshaler
|
||||
}
|
||||
|
||||
// ExportHints are suggestions for storing data.
|
||||
type ExportHints struct {
|
||||
// PartitionKey is a suggested key for partitioning the cache
|
||||
PartitionKey string
|
||||
}
|
||||
|
||||
// ReplaceHints are suggestions for loading data.
|
||||
type ReplaceHints struct {
|
||||
// PartitionKey is a suggested key for partitioning the cache
|
||||
PartitionKey string
|
||||
}
|
||||
|
||||
// ExportReplace exports and replaces in-memory cache data. It doesn't support nil Context or
|
||||
// define the outcome of passing one. A Context without a timeout must receive a default timeout
|
||||
// specified by the implementor. Retries must be implemented inside the implementation.
|
||||
type ExportReplace interface {
|
||||
// Replace replaces the cache with what is in external storage. Implementors should honor
|
||||
// Context cancellations and return context.Canceled or context.DeadlineExceeded in those cases.
|
||||
Replace(ctx context.Context, cache Unmarshaler, hints ReplaceHints) error
|
||||
// Export writes the binary representation of the cache (cache.Marshal()) to external storage.
|
||||
// This is considered opaque. Context cancellations should be honored as in Replace.
|
||||
Export(ctx context.Context, cache Marshaler, hints ExportHints) error
|
||||
}
|
||||
Generated
Vendored
+726
@@ -0,0 +1,726 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package confidential provides a client for authentication of "confidential" applications.
|
||||
A "confidential" application is defined as an app that run on servers. They are considered
|
||||
difficult to access and for that reason capable of keeping an application secret.
|
||||
Confidential clients can hold configuration-time secrets.
|
||||
*/
|
||||
package confidential
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/options"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
|
||||
)
|
||||
|
||||
/*
|
||||
Design note:
|
||||
|
||||
confidential.Client uses base.Client as an embedded type. base.Client statically assigns its attributes
|
||||
during creation. As it doesn't have any pointers in it, anything borrowed from it, such as
|
||||
Base.AuthParams is a copy that is free to be manipulated here.
|
||||
|
||||
Duplicate Calls shared between public.Client and this package:
|
||||
There is some duplicate call options provided here that are the same as in public.Client . This
|
||||
is a design choices. Go proverb(https://www.youtube.com/watch?v=PAAkCSZUG1c&t=9m28s):
|
||||
"a little copying is better than a little dependency". Yes, we could have another package with
|
||||
shared options (fail). That divides like 2 options from all others which makes the user look
|
||||
through more docs. We can have all clients in one package, but I think separate packages
|
||||
here makes for better naming (public.Client vs client.PublicClient). So I chose a little
|
||||
duplication.
|
||||
|
||||
.Net People, Take note on X509:
|
||||
This uses x509.Certificates and private keys. x509 does not store private keys. .Net
|
||||
has a x509.Certificate2 abstraction that has private keys, but that just a strange invention.
|
||||
As such I've put a PEM decoder into here.
|
||||
*/
|
||||
|
||||
// TODO(msal): This should have example code for each method on client using Go's example doc framework.
|
||||
// base usage details should be include in the package documentation.
|
||||
|
||||
// AuthResult contains the results of one token acquisition operation.
|
||||
// For details see https://aka.ms/msal-net-authenticationresult
|
||||
type AuthResult = base.AuthResult
|
||||
|
||||
type AuthenticationScheme = authority.AuthenticationScheme
|
||||
|
||||
type Account = shared.Account
|
||||
|
||||
// CertFromPEM converts a PEM file (.pem or .key) for use with [NewCredFromCert]. The file
|
||||
// must contain the public certificate and the private key. If a PEM block is encrypted and
|
||||
// password is not an empty string, it attempts to decrypt the PEM blocks using the password.
|
||||
// Multiple certs are due to certificate chaining for use cases like TLS that sign from root to leaf.
|
||||
func CertFromPEM(pemData []byte, password string) ([]*x509.Certificate, crypto.PrivateKey, error) {
|
||||
var certs []*x509.Certificate
|
||||
var priv crypto.PrivateKey
|
||||
for {
|
||||
block, rest := pem.Decode(pemData)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
|
||||
//nolint:staticcheck // x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock are deprecated. They are used here only to support a usecase.
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
b, err := x509.DecryptPEMBlock(block, []byte(password))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not decrypt encrypted PEM block: %v", err)
|
||||
}
|
||||
block, _ = pem.Decode(b)
|
||||
if block == nil {
|
||||
return nil, nil, fmt.Errorf("encounter encrypted PEM block that did not decode")
|
||||
}
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "CERTIFICATE":
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("block labelled 'CERTIFICATE' could not be parsed by x509: %v", err)
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
case "PRIVATE KEY":
|
||||
if priv != nil {
|
||||
return nil, nil, errors.New("found multiple private key blocks")
|
||||
}
|
||||
|
||||
var err error
|
||||
priv, err = x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not decode private key: %v", err)
|
||||
}
|
||||
case "RSA PRIVATE KEY":
|
||||
if priv != nil {
|
||||
return nil, nil, errors.New("found multiple private key blocks")
|
||||
}
|
||||
var err error
|
||||
priv, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not decode private key: %v", err)
|
||||
}
|
||||
}
|
||||
pemData = rest
|
||||
}
|
||||
|
||||
if len(certs) == 0 {
|
||||
return nil, nil, fmt.Errorf("no certificates found")
|
||||
}
|
||||
|
||||
if priv == nil {
|
||||
return nil, nil, fmt.Errorf("no private key found")
|
||||
}
|
||||
|
||||
return certs, priv, nil
|
||||
}
|
||||
|
||||
// AssertionRequestOptions has required information for client assertion claims
|
||||
type AssertionRequestOptions = exported.AssertionRequestOptions
|
||||
|
||||
// Credential represents the credential used in confidential client flows.
|
||||
type Credential struct {
|
||||
secret string
|
||||
|
||||
cert *x509.Certificate
|
||||
key crypto.PrivateKey
|
||||
x5c []string
|
||||
|
||||
assertionCallback func(context.Context, AssertionRequestOptions) (string, error)
|
||||
|
||||
tokenProvider func(context.Context, TokenProviderParameters) (TokenProviderResult, error)
|
||||
}
|
||||
|
||||
// toInternal returns the accesstokens.Credential that is used internally. The current structure of the
|
||||
// code requires that client.go, requests.go and confidential.go share a credential type without
|
||||
// having import recursion. That requires the type used between is in a shared package. Therefore
|
||||
// we have this.
|
||||
func (c Credential) toInternal() (*accesstokens.Credential, error) {
|
||||
if c.secret != "" {
|
||||
return &accesstokens.Credential{Secret: c.secret}, nil
|
||||
}
|
||||
if c.cert != nil {
|
||||
if c.key == nil {
|
||||
return nil, errors.New("missing private key for certificate")
|
||||
}
|
||||
return &accesstokens.Credential{Cert: c.cert, Key: c.key, X5c: c.x5c}, nil
|
||||
}
|
||||
if c.key != nil {
|
||||
return nil, errors.New("missing certificate for private key")
|
||||
}
|
||||
if c.assertionCallback != nil {
|
||||
return &accesstokens.Credential{AssertionCallback: c.assertionCallback}, nil
|
||||
}
|
||||
if c.tokenProvider != nil {
|
||||
return &accesstokens.Credential{TokenProvider: c.tokenProvider}, nil
|
||||
}
|
||||
return nil, errors.New("invalid credential")
|
||||
}
|
||||
|
||||
// NewCredFromSecret creates a Credential from a secret.
|
||||
func NewCredFromSecret(secret string) (Credential, error) {
|
||||
if secret == "" {
|
||||
return Credential{}, errors.New("secret can't be empty string")
|
||||
}
|
||||
return Credential{secret: secret}, nil
|
||||
}
|
||||
|
||||
// NewCredFromAssertionCallback creates a Credential that invokes a callback to get assertions
|
||||
// authenticating the application. The callback must be thread safe.
|
||||
func NewCredFromAssertionCallback(callback func(context.Context, AssertionRequestOptions) (string, error)) Credential {
|
||||
return Credential{assertionCallback: callback}
|
||||
}
|
||||
|
||||
// NewCredFromCert creates a Credential from a certificate or chain of certificates and an RSA private key
|
||||
// as returned by [CertFromPEM].
|
||||
func NewCredFromCert(certs []*x509.Certificate, key crypto.PrivateKey) (Credential, error) {
|
||||
cred := Credential{key: key}
|
||||
k, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return cred, errors.New("key must be an RSA key")
|
||||
}
|
||||
for _, cert := range certs {
|
||||
if cert == nil {
|
||||
// not returning an error here because certs may still contain a sufficient cert/key pair
|
||||
continue
|
||||
}
|
||||
certKey, ok := cert.PublicKey.(*rsa.PublicKey)
|
||||
if ok && k.E == certKey.E && k.N.Cmp(certKey.N) == 0 {
|
||||
// We know this is the signing cert because its public key matches the given private key.
|
||||
// This cert must be first in x5c.
|
||||
cred.cert = cert
|
||||
cred.x5c = append([]string{base64.StdEncoding.EncodeToString(cert.Raw)}, cred.x5c...)
|
||||
} else {
|
||||
cred.x5c = append(cred.x5c, base64.StdEncoding.EncodeToString(cert.Raw))
|
||||
}
|
||||
}
|
||||
if cred.cert == nil {
|
||||
return cred, errors.New("key doesn't match any certificate")
|
||||
}
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
// TokenProviderParameters is the authentication parameters passed to token providers
|
||||
type TokenProviderParameters = exported.TokenProviderParameters
|
||||
|
||||
// TokenProviderResult is the authentication result returned by custom token providers
|
||||
type TokenProviderResult = exported.TokenProviderResult
|
||||
|
||||
// NewCredFromTokenProvider creates a Credential from a function that provides access tokens. The function
|
||||
// must be concurrency safe. This is intended only to allow the Azure SDK to cache MSI tokens. It isn't
|
||||
// useful to applications in general because the token provider must implement all authentication logic.
|
||||
func NewCredFromTokenProvider(provider func(context.Context, TokenProviderParameters) (TokenProviderResult, error)) Credential {
|
||||
return Credential{tokenProvider: provider}
|
||||
}
|
||||
|
||||
// AutoDetectRegion instructs MSAL Go to auto detect region for Azure regional token service.
|
||||
func AutoDetectRegion() string {
|
||||
return "TryAutoDetect"
|
||||
}
|
||||
|
||||
// Client is a representation of authentication client for confidential applications as defined in the
|
||||
// package doc. A new Client should be created PER SERVICE USER.
|
||||
// For more information, visit https://docs.microsoft.com/azure/active-directory/develop/msal-client-applications
|
||||
type Client struct {
|
||||
base base.Client
|
||||
cred *accesstokens.Credential
|
||||
}
|
||||
|
||||
// clientOptions are optional settings for New(). These options are set using various functions
|
||||
// returning Option calls.
|
||||
type clientOptions struct {
|
||||
accessor cache.ExportReplace
|
||||
authority, azureRegion string
|
||||
capabilities []string
|
||||
disableInstanceDiscovery, sendX5C bool
|
||||
httpClient ops.HTTPClient
|
||||
}
|
||||
|
||||
// Option is an optional argument to New().
|
||||
type Option func(o *clientOptions)
|
||||
|
||||
// WithCache provides an accessor that will read and write authentication data to an externally managed cache.
|
||||
func WithCache(accessor cache.ExportReplace) Option {
|
||||
return func(o *clientOptions) {
|
||||
o.accessor = accessor
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientCapabilities allows configuring one or more client capabilities such as "CP1"
|
||||
func WithClientCapabilities(capabilities []string) Option {
|
||||
return func(o *clientOptions) {
|
||||
// there's no danger of sharing the slice's underlying memory with the application because
|
||||
// this slice is simply passed to base.WithClientCapabilities, which copies its data
|
||||
o.capabilities = capabilities
|
||||
}
|
||||
}
|
||||
|
||||
// WithHTTPClient allows for a custom HTTP client to be set.
|
||||
func WithHTTPClient(httpClient ops.HTTPClient) Option {
|
||||
return func(o *clientOptions) {
|
||||
o.httpClient = httpClient
|
||||
}
|
||||
}
|
||||
|
||||
// WithX5C specifies if x5c claim(public key of the certificate) should be sent to STS to enable Subject Name Issuer Authentication.
|
||||
func WithX5C() Option {
|
||||
return func(o *clientOptions) {
|
||||
o.sendX5C = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithInstanceDiscovery set to false to disable authority validation (to support private cloud scenarios)
|
||||
func WithInstanceDiscovery(enabled bool) Option {
|
||||
return func(o *clientOptions) {
|
||||
o.disableInstanceDiscovery = !enabled
|
||||
}
|
||||
}
|
||||
|
||||
// WithAzureRegion sets the region(preferred) or Confidential.AutoDetectRegion() for auto detecting region.
|
||||
// Region names as per https://azure.microsoft.com/en-ca/global-infrastructure/geographies/.
|
||||
// See https://aka.ms/region-map for more details on region names.
|
||||
// The region value should be short region name for the region where the service is deployed.
|
||||
// For example "centralus" is short name for region Central US.
|
||||
// Not all auth flows can use the regional token service.
|
||||
// Service To Service (client credential flow) tokens can be obtained from the regional service.
|
||||
// Requires configuration at the tenant level.
|
||||
// Auto-detection works on a limited number of Azure artifacts (VMs, Azure functions).
|
||||
// If auto-detection fails, the non-regional endpoint will be used.
|
||||
// If an invalid region name is provided, the non-regional endpoint MIGHT be used or the token request MIGHT fail.
|
||||
func WithAzureRegion(val string) Option {
|
||||
return func(o *clientOptions) {
|
||||
o.azureRegion = val
|
||||
}
|
||||
}
|
||||
|
||||
// New is the constructor for Client. authority is the URL of a token authority such as "https://login.microsoftonline.com/<your tenant>".
|
||||
// If the Client will connect directly to AD FS, use "adfs" for the tenant. clientID is the application's client ID (also called its
|
||||
// "application ID").
|
||||
func New(authority, clientID string, cred Credential, options ...Option) (Client, error) {
|
||||
internalCred, err := cred.toInternal()
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
autoEnabledRegion := os.Getenv("MSAL_FORCE_REGION")
|
||||
opts := clientOptions{
|
||||
authority: authority,
|
||||
// if the caller specified a token provider, it will handle all details of authentication, using Client only as a token cache
|
||||
disableInstanceDiscovery: cred.tokenProvider != nil,
|
||||
httpClient: shared.DefaultClient,
|
||||
azureRegion: autoEnabledRegion,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(&opts)
|
||||
}
|
||||
if strings.EqualFold(opts.azureRegion, "DisableMsalForceRegion") {
|
||||
opts.azureRegion = ""
|
||||
}
|
||||
|
||||
baseOpts := []base.Option{
|
||||
base.WithCacheAccessor(opts.accessor),
|
||||
base.WithClientCapabilities(opts.capabilities),
|
||||
base.WithInstanceDiscovery(!opts.disableInstanceDiscovery),
|
||||
base.WithRegionDetection(opts.azureRegion),
|
||||
base.WithX5C(opts.sendX5C),
|
||||
}
|
||||
base, err := base.New(clientID, opts.authority, oauth.New(opts.httpClient), baseOpts...)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
base.AuthParams.IsConfidentialClient = true
|
||||
|
||||
return Client{base: base, cred: internalCred}, nil
|
||||
}
|
||||
|
||||
// authCodeURLOptions contains options for AuthCodeURL
|
||||
type authCodeURLOptions struct {
|
||||
claims, loginHint, tenantID, domainHint string
|
||||
}
|
||||
|
||||
// AuthCodeURLOption is implemented by options for AuthCodeURL
|
||||
type AuthCodeURLOption interface {
|
||||
authCodeURLOption()
|
||||
}
|
||||
|
||||
// AuthCodeURL creates a URL used to acquire an authorization code. Users need to call CreateAuthorizationCodeURLParameters and pass it in.
|
||||
//
|
||||
// Options: [WithClaims], [WithDomainHint], [WithLoginHint], [WithTenantID]
|
||||
func (cca Client) AuthCodeURL(ctx context.Context, clientID, redirectURI string, scopes []string, opts ...AuthCodeURLOption) (string, error) {
|
||||
o := authCodeURLOptions{}
|
||||
if err := options.ApplyOptions(&o, opts); err != nil {
|
||||
return "", err
|
||||
}
|
||||
ap, err := cca.base.AuthParams.WithTenant(o.tenantID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ap.Claims = o.claims
|
||||
ap.LoginHint = o.loginHint
|
||||
ap.DomainHint = o.domainHint
|
||||
return cca.base.AuthCodeURL(ctx, clientID, redirectURI, scopes, ap)
|
||||
}
|
||||
|
||||
// WithLoginHint pre-populates the login prompt with a username.
|
||||
func WithLoginHint(username string) interface {
|
||||
AuthCodeURLOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AuthCodeURLOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *authCodeURLOptions:
|
||||
t.loginHint = username
|
||||
default:
|
||||
return fmt.Errorf("unexpected options type %T", a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// WithDomainHint adds the IdP domain as domain_hint query parameter in the auth url.
|
||||
func WithDomainHint(domain string) interface {
|
||||
AuthCodeURLOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AuthCodeURLOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *authCodeURLOptions:
|
||||
t.domainHint = domain
|
||||
default:
|
||||
return fmt.Errorf("unexpected options type %T", a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// WithClaims sets additional claims to request for the token, such as those required by conditional access policies.
|
||||
// Use this option when Azure AD returned a claims challenge for a prior request. The argument must be decoded.
|
||||
// This option is valid for any token acquisition method.
|
||||
func WithClaims(claims string) interface {
|
||||
AcquireByAuthCodeOption
|
||||
AcquireByCredentialOption
|
||||
AcquireOnBehalfOfOption
|
||||
AcquireSilentOption
|
||||
AuthCodeURLOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireByAuthCodeOption
|
||||
AcquireByCredentialOption
|
||||
AcquireOnBehalfOfOption
|
||||
AcquireSilentOption
|
||||
AuthCodeURLOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *acquireTokenByAuthCodeOptions:
|
||||
t.claims = claims
|
||||
case *acquireTokenByCredentialOptions:
|
||||
t.claims = claims
|
||||
case *acquireTokenOnBehalfOfOptions:
|
||||
t.claims = claims
|
||||
case *acquireTokenSilentOptions:
|
||||
t.claims = claims
|
||||
case *authCodeURLOptions:
|
||||
t.claims = claims
|
||||
default:
|
||||
return fmt.Errorf("unexpected options type %T", a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthenticationScheme is an extensibility mechanism designed to be used only by Azure Arc for proof of possession access tokens.
|
||||
func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface {
|
||||
AcquireSilentOption
|
||||
AcquireByCredentialOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireSilentOption
|
||||
AcquireByCredentialOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *acquireTokenSilentOptions:
|
||||
t.authnScheme = authnScheme
|
||||
case *acquireTokenByCredentialOptions:
|
||||
t.authnScheme = authnScheme
|
||||
default:
|
||||
return fmt.Errorf("unexpected options type %T", a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// WithTenantID specifies a tenant for a single authentication. It may be different than the tenant set in [New].
|
||||
// This option is valid for any token acquisition method.
|
||||
func WithTenantID(tenantID string) interface {
|
||||
AcquireByAuthCodeOption
|
||||
AcquireByCredentialOption
|
||||
AcquireOnBehalfOfOption
|
||||
AcquireSilentOption
|
||||
AuthCodeURLOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireByAuthCodeOption
|
||||
AcquireByCredentialOption
|
||||
AcquireOnBehalfOfOption
|
||||
AcquireSilentOption
|
||||
AuthCodeURLOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *acquireTokenByAuthCodeOptions:
|
||||
t.tenantID = tenantID
|
||||
case *acquireTokenByCredentialOptions:
|
||||
t.tenantID = tenantID
|
||||
case *acquireTokenOnBehalfOfOptions:
|
||||
t.tenantID = tenantID
|
||||
case *acquireTokenSilentOptions:
|
||||
t.tenantID = tenantID
|
||||
case *authCodeURLOptions:
|
||||
t.tenantID = tenantID
|
||||
default:
|
||||
return fmt.Errorf("unexpected options type %T", a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// acquireTokenSilentOptions are all the optional settings to an AcquireTokenSilent() call.
|
||||
// These are set by using various AcquireTokenSilentOption functions.
|
||||
type acquireTokenSilentOptions struct {
|
||||
account Account
|
||||
claims, tenantID string
|
||||
authnScheme AuthenticationScheme
|
||||
}
|
||||
|
||||
// AcquireSilentOption is implemented by options for AcquireTokenSilent
|
||||
type AcquireSilentOption interface {
|
||||
acquireSilentOption()
|
||||
}
|
||||
|
||||
// WithSilentAccount uses the passed account during an AcquireTokenSilent() call.
|
||||
func WithSilentAccount(account Account) interface {
|
||||
AcquireSilentOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireSilentOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *acquireTokenSilentOptions:
|
||||
t.account = account
|
||||
default:
|
||||
return fmt.Errorf("unexpected options type %T", a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// AcquireTokenSilent acquires a token from either the cache or using a refresh token.
|
||||
//
|
||||
// Options: [WithClaims], [WithSilentAccount], [WithTenantID]
|
||||
func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts ...AcquireSilentOption) (AuthResult, error) {
|
||||
o := acquireTokenSilentOptions{}
|
||||
if err := options.ApplyOptions(&o, opts); err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
if o.claims != "" {
|
||||
return AuthResult{}, errors.New("call another AcquireToken method to request a new token having these claims")
|
||||
}
|
||||
|
||||
silentParameters := base.AcquireTokenSilentParameters{
|
||||
Scopes: scopes,
|
||||
Account: o.account,
|
||||
RequestType: accesstokens.ATConfidential,
|
||||
Credential: cca.cred,
|
||||
IsAppCache: o.account.IsZero(),
|
||||
TenantID: o.tenantID,
|
||||
AuthnScheme: o.authnScheme,
|
||||
}
|
||||
|
||||
return cca.base.AcquireTokenSilent(ctx, silentParameters)
|
||||
}
|
||||
|
||||
// acquireTokenByAuthCodeOptions contains the optional parameters used to acquire an access token using the authorization code flow.
|
||||
type acquireTokenByAuthCodeOptions struct {
|
||||
challenge, claims, tenantID string
|
||||
}
|
||||
|
||||
// AcquireByAuthCodeOption is implemented by options for AcquireTokenByAuthCode
|
||||
type AcquireByAuthCodeOption interface {
|
||||
acquireByAuthCodeOption()
|
||||
}
|
||||
|
||||
// WithChallenge allows you to provide a challenge for the .AcquireTokenByAuthCode() call.
|
||||
func WithChallenge(challenge string) interface {
|
||||
AcquireByAuthCodeOption
|
||||
options.CallOption
|
||||
} {
|
||||
return struct {
|
||||
AcquireByAuthCodeOption
|
||||
options.CallOption
|
||||
}{
|
||||
CallOption: options.NewCallOption(
|
||||
func(a any) error {
|
||||
switch t := a.(type) {
|
||||
case *acquireTokenByAuthCodeOptions:
|
||||
t.challenge = challenge
|
||||
default:
|
||||
return fmt.Errorf("unexpected options type %T", a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// AcquireTokenByAuthCode is a request to acquire a security token from the authority, using an authorization code.
|
||||
// The specified redirect URI must be the same URI that was used when the authorization code was requested.
|
||||
//
|
||||
// Options: [WithChallenge], [WithClaims], [WithTenantID]
|
||||
func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, opts ...AcquireByAuthCodeOption) (AuthResult, error) {
|
||||
o := acquireTokenByAuthCodeOptions{}
|
||||
if err := options.ApplyOptions(&o, opts); err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
params := base.AcquireTokenAuthCodeParameters{
|
||||
Scopes: scopes,
|
||||
Code: code,
|
||||
Challenge: o.challenge,
|
||||
Claims: o.claims,
|
||||
AppType: accesstokens.ATConfidential,
|
||||
Credential: cca.cred, // This setting differs from public.Client.AcquireTokenByAuthCode
|
||||
RedirectURI: redirectURI,
|
||||
TenantID: o.tenantID,
|
||||
}
|
||||
|
||||
return cca.base.AcquireTokenByAuthCode(ctx, params)
|
||||
}
|
||||
|
||||
// acquireTokenByCredentialOptions contains optional configuration for AcquireTokenByCredential
|
||||
type acquireTokenByCredentialOptions struct {
|
||||
claims, tenantID string
|
||||
authnScheme AuthenticationScheme
|
||||
}
|
||||
|
||||
// AcquireByCredentialOption is implemented by options for AcquireTokenByCredential
|
||||
type AcquireByCredentialOption interface {
|
||||
acquireByCredOption()
|
||||
}
|
||||
|
||||
// AcquireTokenByCredential acquires a security token from the authority, using the client credentials grant.
|
||||
//
|
||||
// Options: [WithClaims], [WithTenantID]
|
||||
func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string, opts ...AcquireByCredentialOption) (AuthResult, error) {
|
||||
o := acquireTokenByCredentialOptions{}
|
||||
err := options.ApplyOptions(&o, opts)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams, err := cca.base.AuthParams.WithTenant(o.tenantID)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams.Scopes = scopes
|
||||
authParams.AuthorizationType = authority.ATClientCredentials
|
||||
authParams.Claims = o.claims
|
||||
if o.authnScheme != nil {
|
||||
authParams.AuthnScheme = o.authnScheme
|
||||
}
|
||||
token, err := cca.base.Token.Credential(ctx, authParams, cca.cred)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
return cca.base.AuthResultFromToken(ctx, authParams, token, true)
|
||||
}
|
||||
|
||||
// acquireTokenOnBehalfOfOptions contains optional configuration for AcquireTokenOnBehalfOf
|
||||
type acquireTokenOnBehalfOfOptions struct {
|
||||
claims, tenantID string
|
||||
}
|
||||
|
||||
// AcquireOnBehalfOfOption is implemented by options for AcquireTokenOnBehalfOf
|
||||
type AcquireOnBehalfOfOption interface {
|
||||
acquireOBOOption()
|
||||
}
|
||||
|
||||
// AcquireTokenOnBehalfOf acquires a security token for an app using middle tier apps access token.
|
||||
// Refer https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow.
|
||||
//
|
||||
// Options: [WithClaims], [WithTenantID]
|
||||
func (cca Client) AcquireTokenOnBehalfOf(ctx context.Context, userAssertion string, scopes []string, opts ...AcquireOnBehalfOfOption) (AuthResult, error) {
|
||||
o := acquireTokenOnBehalfOfOptions{}
|
||||
if err := options.ApplyOptions(&o, opts); err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
params := base.AcquireTokenOnBehalfOfParameters{
|
||||
Scopes: scopes,
|
||||
UserAssertion: userAssertion,
|
||||
Claims: o.claims,
|
||||
Credential: cca.cred,
|
||||
TenantID: o.tenantID,
|
||||
}
|
||||
return cca.base.AcquireTokenOnBehalfOf(ctx, params)
|
||||
}
|
||||
|
||||
// Account gets the account in the token cache with the specified homeAccountID.
|
||||
func (cca Client) Account(ctx context.Context, accountID string) (Account, error) {
|
||||
return cca.base.Account(ctx, accountID)
|
||||
}
|
||||
|
||||
// RemoveAccount signs the account out and forgets account from token cache.
|
||||
func (cca Client) RemoveAccount(ctx context.Context, account Account) error {
|
||||
return cca.base.RemoveAccount(ctx, account)
|
||||
}
|
||||
Generated
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
# MSAL Error Design
|
||||
|
||||
Author: Abhidnya Patil(abhidnya.patil@microsoft.com)
|
||||
|
||||
Contributors:
|
||||
|
||||
- John Doak(jdoak@microsoft.com)
|
||||
- Keegan Caruso(Keegan.Caruso@microsoft.com)
|
||||
- Joel Hendrix(jhendrix@microsoft.com)
|
||||
|
||||
## Background
|
||||
|
||||
Errors in MSAL are intended for app developers to troubleshoot and not for displaying to end-users.
|
||||
|
||||
### Go error handling vs other MSAL languages
|
||||
|
||||
Most modern languages use exception based errors. Simply put, you "throw" an exception and it must be caught at some routine in the upper stack or it will eventually crash the program.
|
||||
|
||||
Go doesn't use exceptions, instead it relies on multiple return values, one of which can be the builtin error interface type. It is up to the user to decide what to do.
|
||||
|
||||
### Go custom error types
|
||||
|
||||
Errors can be created in Go by simply using errors.New() or fmt.Errorf() to create an "error".
|
||||
|
||||
Custom errors can be created in multiple ways. One of the more robust ways is simply to satisfy the error interface:
|
||||
|
||||
```go
|
||||
type MyCustomErr struct {
|
||||
Msg string
|
||||
}
|
||||
func (m MyCustomErr) Error() string { // This implements "error"
|
||||
return m.Msg
|
||||
}
|
||||
```
|
||||
|
||||
### MSAL Error Goals
|
||||
|
||||
- Provide diagnostics to the user and for tickets that can be used to track down bugs or client misconfigurations
|
||||
- Detect errors that are transitory and can be retried
|
||||
- Allow the user to identify certain errors that the program can respond to, such a informing the user for the need to do an enrollment
|
||||
|
||||
## Implementing Client Side Errors
|
||||
|
||||
Client side errors indicate a misconfiguration or passing of bad arguments that is non-recoverable. Retrying isn't possible.
|
||||
|
||||
These errors can simply be standard Go errors created by errors.New() or fmt.Errorf(). If down the line we need a custom error, we can introduce it, but for now the error messages just need to be clear on what the issue was.
|
||||
|
||||
## Implementing Service Side Errors
|
||||
|
||||
Service side errors occur when an external RPC responds either with an HTTP error code or returns a message that includes an error.
|
||||
|
||||
These errors can be transitory (please slow down) or permanent (HTTP 404). To provide our diagnostic goals, we require the ability to differentiate these errors from other errors.
|
||||
|
||||
The current implementation includes a specialized type that captures any error from the server:
|
||||
|
||||
```go
|
||||
// CallErr represents an HTTP call error. Has a Verbose() method that allows getting the
|
||||
// http.Request and Response objects. Implements error.
|
||||
type CallErr struct {
|
||||
Req *http.Request
|
||||
Resp *http.Response
|
||||
Err error
|
||||
}
|
||||
|
||||
// Errors implements error.Error().
|
||||
func (e CallErr) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Verbose prints a versbose error message with the request or response.
|
||||
func (e CallErr) Verbose() string {
|
||||
e.Resp.Request = nil // This brings in a bunch of TLS stuff we don't need
|
||||
e.Resp.TLS = nil // Same
|
||||
return fmt.Sprintf("%s:\nRequest:\n%s\nResponse:\n%s", e.Err, prettyConf.Sprint(e.Req), prettyConf.Sprint(e.Resp))
|
||||
}
|
||||
```
|
||||
|
||||
A user will always receive the most concise error we provide. They can tell if it is a server side error using Go error package:
|
||||
|
||||
```go
|
||||
var callErr CallErr
|
||||
if errors.As(err, &callErr) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
We provide a Verbose() function that can retrieve the most verbose message from any error we provide:
|
||||
|
||||
```go
|
||||
fmt.Println(errors.Verbose(err))
|
||||
```
|
||||
|
||||
If further differentiation is required, we can add custom errors that use Go error wrapping on top of CallErr to achieve our diagnostic goals (such as detecting when to retry a call due to transient errors).
|
||||
|
||||
CallErr is always thrown from the comm package (which handles all http requests) and looks similar to:
|
||||
|
||||
```go
|
||||
return nil, errors.CallErr{
|
||||
Req: req,
|
||||
Resp: reply,
|
||||
Err: fmt.Errorf("http call(%s)(%s) error: reply status code was %d:\n%s", req.URL.String(), req.Method, reply.StatusCode, ErrorResponse), //ErrorResponse is the json body extracted from the http response
|
||||
}
|
||||
```
|
||||
|
||||
## Future Decisions
|
||||
|
||||
The ability to retry calls needs to have centralized responsibility. Either the user is doing it or the client is doing it.
|
||||
|
||||
If the user should be responsible, our errors package will include a CanRetry() function that will inform the user if the error provided to them is retryable. This is based on the http error code and possibly the type of error that was returned. It would also include a sleep time if the server returned an amount of time to wait.
|
||||
|
||||
Otherwise we will do this internally and retries will be left to us.
|
||||
Generated
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
var prettyConf = &pretty.Config{
|
||||
IncludeUnexported: false,
|
||||
SkipZeroFields: true,
|
||||
TrackCycles: true,
|
||||
Formatter: map[reflect.Type]interface{}{
|
||||
reflect.TypeOf((*io.Reader)(nil)).Elem(): func(r io.Reader) string {
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return "could not read io.Reader content"
|
||||
}
|
||||
return string(b)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type verboser interface {
|
||||
Verbose() string
|
||||
}
|
||||
|
||||
// Verbose prints the most verbose error that the error message has.
|
||||
func Verbose(err error) string {
|
||||
build := strings.Builder{}
|
||||
for {
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if v, ok := err.(verboser); ok {
|
||||
build.WriteString(v.Verbose())
|
||||
} else {
|
||||
build.WriteString(err.Error())
|
||||
}
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return build.String()
|
||||
}
|
||||
|
||||
// New is equivalent to errors.New().
|
||||
func New(text string) error {
|
||||
return errors.New(text)
|
||||
}
|
||||
|
||||
// CallErr represents an HTTP call error. Has a Verbose() method that allows getting the
|
||||
// http.Request and Response objects. Implements error.
|
||||
type CallErr struct {
|
||||
Req *http.Request
|
||||
// Resp contains response body
|
||||
Resp *http.Response
|
||||
Err error
|
||||
}
|
||||
|
||||
// Errors implements error.Error().
|
||||
func (e CallErr) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Verbose prints a versbose error message with the request or response.
|
||||
func (e CallErr) Verbose() string {
|
||||
e.Resp.Request = nil // This brings in a bunch of TLS crap we don't need
|
||||
e.Resp.TLS = nil // Same
|
||||
return fmt.Sprintf("%s:\nRequest:\n%s\nResponse:\n%s", e.Err, prettyConf.Sprint(e.Req), prettyConf.Sprint(e.Resp))
|
||||
}
|
||||
|
||||
// Is reports whether any error in errors chain matches target.
|
||||
func Is(err, target error) bool {
|
||||
return errors.Is(err, target)
|
||||
}
|
||||
|
||||
// As finds the first error in errors chain that matches target,
|
||||
// and if so, sets target to that error value and returns true.
|
||||
// Otherwise, it returns false.
|
||||
func As(err error, target interface{}) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
backend/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go
Generated
Vendored
+505
@@ -0,0 +1,505 @@
|
||||
// Package base contains a "Base" client that is used by the external public.Client and confidential.Client.
|
||||
// Base holds shared attributes that must be available to both clients and methods that act as
|
||||
// shared calls.
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
// AuthorityPublicCloud is the default AAD authority host
|
||||
AuthorityPublicCloud = "https://login.microsoftonline.com/common"
|
||||
scopeSeparator = " "
|
||||
)
|
||||
|
||||
// manager provides an internal cache. It is defined to allow faking the cache in tests.
|
||||
// In production it's a *storage.Manager or *storage.PartitionedManager.
|
||||
type manager interface {
|
||||
cache.Serializer
|
||||
Read(context.Context, authority.AuthParams) (storage.TokenResponse, error)
|
||||
Write(authority.AuthParams, accesstokens.TokenResponse) (shared.Account, error)
|
||||
}
|
||||
|
||||
// accountManager is a manager that also caches accounts. In production it's a *storage.Manager.
|
||||
type accountManager interface {
|
||||
manager
|
||||
AllAccounts() []shared.Account
|
||||
Account(homeAccountID string) shared.Account
|
||||
RemoveAccount(account shared.Account, clientID string)
|
||||
}
|
||||
|
||||
// AcquireTokenSilentParameters contains the parameters to acquire a token silently (from cache).
|
||||
type AcquireTokenSilentParameters struct {
|
||||
Scopes []string
|
||||
Account shared.Account
|
||||
RequestType accesstokens.AppType
|
||||
Credential *accesstokens.Credential
|
||||
IsAppCache bool
|
||||
TenantID string
|
||||
UserAssertion string
|
||||
AuthorizationType authority.AuthorizeType
|
||||
Claims string
|
||||
AuthnScheme authority.AuthenticationScheme
|
||||
}
|
||||
|
||||
// AcquireTokenAuthCodeParameters contains the parameters required to acquire an access token using the auth code flow.
|
||||
// To use PKCE, set the CodeChallengeParameter.
|
||||
// Code challenges are used to secure authorization code grants; for more information, visit
|
||||
// https://tools.ietf.org/html/rfc7636.
|
||||
type AcquireTokenAuthCodeParameters struct {
|
||||
Scopes []string
|
||||
Code string
|
||||
Challenge string
|
||||
Claims string
|
||||
RedirectURI string
|
||||
AppType accesstokens.AppType
|
||||
Credential *accesstokens.Credential
|
||||
TenantID string
|
||||
}
|
||||
|
||||
type AcquireTokenOnBehalfOfParameters struct {
|
||||
Scopes []string
|
||||
Claims string
|
||||
Credential *accesstokens.Credential
|
||||
TenantID string
|
||||
UserAssertion string
|
||||
}
|
||||
|
||||
// AuthResult contains the results of one token acquisition operation in PublicClientApplication
|
||||
// or ConfidentialClientApplication. For details see https://aka.ms/msal-net-authenticationresult
|
||||
type AuthResult struct {
|
||||
Account shared.Account
|
||||
IDToken accesstokens.IDToken
|
||||
AccessToken string
|
||||
ExpiresOn time.Time
|
||||
GrantedScopes []string
|
||||
DeclinedScopes []string
|
||||
Metadata AuthResultMetadata
|
||||
}
|
||||
|
||||
// AuthResultMetadata which contains meta data for the AuthResult
|
||||
type AuthResultMetadata struct {
|
||||
TokenSource TokenSource
|
||||
}
|
||||
|
||||
type TokenSource int
|
||||
|
||||
// These are all the types of token flows.
|
||||
const (
|
||||
SourceUnknown TokenSource = 0
|
||||
IdentityProvider TokenSource = 1
|
||||
Cache TokenSource = 2
|
||||
)
|
||||
|
||||
// AuthResultFromStorage creates an AuthResult from a storage token response (which is generated from the cache).
|
||||
func AuthResultFromStorage(storageTokenResponse storage.TokenResponse) (AuthResult, error) {
|
||||
if err := storageTokenResponse.AccessToken.Validate(); err != nil {
|
||||
return AuthResult{}, fmt.Errorf("problem with access token in StorageTokenResponse: %w", err)
|
||||
}
|
||||
|
||||
account := storageTokenResponse.Account
|
||||
accessToken := storageTokenResponse.AccessToken.Secret
|
||||
grantedScopes := strings.Split(storageTokenResponse.AccessToken.Scopes, scopeSeparator)
|
||||
|
||||
// Checking if there was an ID token in the cache; this will throw an error in the case of confidential client applications.
|
||||
var idToken accesstokens.IDToken
|
||||
if !storageTokenResponse.IDToken.IsZero() {
|
||||
err := idToken.UnmarshalJSON([]byte(storageTokenResponse.IDToken.Secret))
|
||||
if err != nil {
|
||||
return AuthResult{}, fmt.Errorf("problem decoding JWT token: %w", err)
|
||||
}
|
||||
}
|
||||
return AuthResult{
|
||||
Account: account,
|
||||
IDToken: idToken,
|
||||
AccessToken: accessToken,
|
||||
ExpiresOn: storageTokenResponse.AccessToken.ExpiresOn.T,
|
||||
GrantedScopes: grantedScopes,
|
||||
DeclinedScopes: nil,
|
||||
Metadata: AuthResultMetadata{
|
||||
TokenSource: Cache,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewAuthResult creates an AuthResult.
|
||||
func NewAuthResult(tokenResponse accesstokens.TokenResponse, account shared.Account) (AuthResult, error) {
|
||||
if len(tokenResponse.DeclinedScopes) > 0 {
|
||||
return AuthResult{}, fmt.Errorf("token response failed because declined scopes are present: %s", strings.Join(tokenResponse.DeclinedScopes, ","))
|
||||
}
|
||||
return AuthResult{
|
||||
Account: account,
|
||||
IDToken: tokenResponse.IDToken,
|
||||
AccessToken: tokenResponse.AccessToken,
|
||||
ExpiresOn: tokenResponse.ExpiresOn.T,
|
||||
GrantedScopes: tokenResponse.GrantedScopes.Slice,
|
||||
Metadata: AuthResultMetadata{
|
||||
TokenSource: IdentityProvider,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Client is a base client that provides access to common methods and primatives that
|
||||
// can be used by multiple clients.
|
||||
type Client struct {
|
||||
Token *oauth.Client
|
||||
manager accountManager // *storage.Manager or fakeManager in tests
|
||||
// pmanager is a partitioned cache for OBO authentication. *storage.PartitionedManager or fakeManager in tests
|
||||
pmanager manager
|
||||
|
||||
AuthParams authority.AuthParams // DO NOT EVER MAKE THIS A POINTER! See "Note" in New().
|
||||
cacheAccessor cache.ExportReplace
|
||||
cacheAccessorMu *sync.RWMutex
|
||||
}
|
||||
|
||||
// Option is an optional argument to the New constructor.
|
||||
type Option func(c *Client) error
|
||||
|
||||
// WithCacheAccessor allows you to set some type of cache for storing authentication tokens.
|
||||
func WithCacheAccessor(ca cache.ExportReplace) Option {
|
||||
return func(c *Client) error {
|
||||
if ca != nil {
|
||||
c.cacheAccessor = ca
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientCapabilities allows configuring one or more client capabilities such as "CP1"
|
||||
func WithClientCapabilities(capabilities []string) Option {
|
||||
return func(c *Client) error {
|
||||
var err error
|
||||
if len(capabilities) > 0 {
|
||||
cc, err := authority.NewClientCapabilities(capabilities)
|
||||
if err == nil {
|
||||
c.AuthParams.Capabilities = cc
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithKnownAuthorityHosts specifies hosts Client shouldn't validate or request metadata for because they're known to the user
|
||||
func WithKnownAuthorityHosts(hosts []string) Option {
|
||||
return func(c *Client) error {
|
||||
cp := make([]string, len(hosts))
|
||||
copy(cp, hosts)
|
||||
c.AuthParams.KnownAuthorityHosts = cp
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithX5C specifies if x5c claim(public key of the certificate) should be sent to STS to enable Subject Name Issuer Authentication.
|
||||
func WithX5C(sendX5C bool) Option {
|
||||
return func(c *Client) error {
|
||||
c.AuthParams.SendX5C = sendX5C
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithRegionDetection(region string) Option {
|
||||
return func(c *Client) error {
|
||||
c.AuthParams.AuthorityInfo.Region = region
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithInstanceDiscovery(instanceDiscoveryEnabled bool) Option {
|
||||
return func(c *Client) error {
|
||||
c.AuthParams.AuthorityInfo.ValidateAuthority = instanceDiscoveryEnabled
|
||||
c.AuthParams.AuthorityInfo.InstanceDiscoveryDisabled = !instanceDiscoveryEnabled
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// New is the constructor for Base.
|
||||
func New(clientID string, authorityURI string, token *oauth.Client, options ...Option) (Client, error) {
|
||||
//By default, validateAuthority is set to true and instanceDiscoveryDisabled is set to false
|
||||
authInfo, err := authority.NewInfoFromAuthorityURI(authorityURI, true, false)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
authParams := authority.NewAuthParams(clientID, authInfo)
|
||||
client := Client{ // Note: Hey, don't even THINK about making Base into *Base. See "design notes" in public.go and confidential.go
|
||||
Token: token,
|
||||
AuthParams: authParams,
|
||||
cacheAccessorMu: &sync.RWMutex{},
|
||||
manager: storage.New(token),
|
||||
pmanager: storage.NewPartitionedManager(token),
|
||||
}
|
||||
for _, o := range options {
|
||||
if err = o(&client); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return client, err
|
||||
|
||||
}
|
||||
|
||||
// AuthCodeURL creates a URL used to acquire an authorization code.
|
||||
func (b Client) AuthCodeURL(ctx context.Context, clientID, redirectURI string, scopes []string, authParams authority.AuthParams) (string, error) {
|
||||
endpoints, err := b.Token.ResolveEndpoints(ctx, authParams.AuthorityInfo, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseURL, err := url.Parse(endpoints.AuthorizationEndpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
claims, err := authParams.MergeCapabilitiesAndClaims()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Add("client_id", clientID)
|
||||
v.Add("response_type", "code")
|
||||
v.Add("redirect_uri", redirectURI)
|
||||
v.Add("scope", strings.Join(scopes, scopeSeparator))
|
||||
if authParams.State != "" {
|
||||
v.Add("state", authParams.State)
|
||||
}
|
||||
if claims != "" {
|
||||
v.Add("claims", claims)
|
||||
}
|
||||
if authParams.CodeChallenge != "" {
|
||||
v.Add("code_challenge", authParams.CodeChallenge)
|
||||
}
|
||||
if authParams.CodeChallengeMethod != "" {
|
||||
v.Add("code_challenge_method", authParams.CodeChallengeMethod)
|
||||
}
|
||||
if authParams.LoginHint != "" {
|
||||
v.Add("login_hint", authParams.LoginHint)
|
||||
}
|
||||
if authParams.Prompt != "" {
|
||||
v.Add("prompt", authParams.Prompt)
|
||||
}
|
||||
if authParams.DomainHint != "" {
|
||||
v.Add("domain_hint", authParams.DomainHint)
|
||||
}
|
||||
// There were left over from an implementation that didn't use any of these. We may
|
||||
// need to add them later, but as of now aren't needed.
|
||||
/*
|
||||
if p.ResponseMode != "" {
|
||||
urlParams.Add("response_mode", p.ResponseMode)
|
||||
}
|
||||
*/
|
||||
baseURL.RawQuery = v.Encode()
|
||||
return baseURL.String(), nil
|
||||
}
|
||||
|
||||
func (b Client) AcquireTokenSilent(ctx context.Context, silent AcquireTokenSilentParameters) (AuthResult, error) {
|
||||
ar := AuthResult{}
|
||||
// when tenant == "", the caller didn't specify a tenant and WithTenant will choose the client's configured tenant
|
||||
tenant := silent.TenantID
|
||||
authParams, err := b.AuthParams.WithTenant(tenant)
|
||||
if err != nil {
|
||||
return ar, err
|
||||
}
|
||||
authParams.Scopes = silent.Scopes
|
||||
authParams.HomeAccountID = silent.Account.HomeAccountID
|
||||
authParams.AuthorizationType = silent.AuthorizationType
|
||||
authParams.Claims = silent.Claims
|
||||
authParams.UserAssertion = silent.UserAssertion
|
||||
if silent.AuthnScheme != nil {
|
||||
authParams.AuthnScheme = silent.AuthnScheme
|
||||
}
|
||||
|
||||
m := b.pmanager
|
||||
if authParams.AuthorizationType != authority.ATOnBehalfOf {
|
||||
authParams.AuthorizationType = authority.ATRefreshToken
|
||||
m = b.manager
|
||||
}
|
||||
if b.cacheAccessor != nil {
|
||||
key := authParams.CacheKey(silent.IsAppCache)
|
||||
b.cacheAccessorMu.RLock()
|
||||
err = b.cacheAccessor.Replace(ctx, m, cache.ReplaceHints{PartitionKey: key})
|
||||
b.cacheAccessorMu.RUnlock()
|
||||
}
|
||||
if err != nil {
|
||||
return ar, err
|
||||
}
|
||||
storageTokenResponse, err := m.Read(ctx, authParams)
|
||||
if err != nil {
|
||||
return ar, err
|
||||
}
|
||||
|
||||
// ignore cached access tokens when given claims
|
||||
if silent.Claims == "" {
|
||||
ar, err = AuthResultFromStorage(storageTokenResponse)
|
||||
if err == nil {
|
||||
ar.AccessToken, err = authParams.AuthnScheme.FormatAccessToken(ar.AccessToken)
|
||||
return ar, err
|
||||
}
|
||||
}
|
||||
|
||||
// redeem a cached refresh token, if available
|
||||
if reflect.ValueOf(storageTokenResponse.RefreshToken).IsZero() {
|
||||
return ar, errors.New("no token found")
|
||||
}
|
||||
var cc *accesstokens.Credential
|
||||
if silent.RequestType == accesstokens.ATConfidential {
|
||||
cc = silent.Credential
|
||||
}
|
||||
token, err := b.Token.Refresh(ctx, silent.RequestType, authParams, cc, storageTokenResponse.RefreshToken)
|
||||
if err != nil {
|
||||
return ar, err
|
||||
}
|
||||
return b.AuthResultFromToken(ctx, authParams, token, true)
|
||||
}
|
||||
|
||||
func (b Client) AcquireTokenByAuthCode(ctx context.Context, authCodeParams AcquireTokenAuthCodeParameters) (AuthResult, error) {
|
||||
authParams, err := b.AuthParams.WithTenant(authCodeParams.TenantID)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams.Claims = authCodeParams.Claims
|
||||
authParams.Scopes = authCodeParams.Scopes
|
||||
authParams.Redirecturi = authCodeParams.RedirectURI
|
||||
authParams.AuthorizationType = authority.ATAuthCode
|
||||
|
||||
var cc *accesstokens.Credential
|
||||
if authCodeParams.AppType == accesstokens.ATConfidential {
|
||||
cc = authCodeParams.Credential
|
||||
authParams.IsConfidentialClient = true
|
||||
}
|
||||
|
||||
req, err := accesstokens.NewCodeChallengeRequest(authParams, authCodeParams.AppType, cc, authCodeParams.Code, authCodeParams.Challenge)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
token, err := b.Token.AuthCode(ctx, req)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
return b.AuthResultFromToken(ctx, authParams, token, true)
|
||||
}
|
||||
|
||||
// AcquireTokenOnBehalfOf acquires a security token for an app using middle tier apps access token.
|
||||
func (b Client) AcquireTokenOnBehalfOf(ctx context.Context, onBehalfOfParams AcquireTokenOnBehalfOfParameters) (AuthResult, error) {
|
||||
var ar AuthResult
|
||||
silentParameters := AcquireTokenSilentParameters{
|
||||
Scopes: onBehalfOfParams.Scopes,
|
||||
RequestType: accesstokens.ATConfidential,
|
||||
Credential: onBehalfOfParams.Credential,
|
||||
UserAssertion: onBehalfOfParams.UserAssertion,
|
||||
AuthorizationType: authority.ATOnBehalfOf,
|
||||
TenantID: onBehalfOfParams.TenantID,
|
||||
Claims: onBehalfOfParams.Claims,
|
||||
}
|
||||
ar, err := b.AcquireTokenSilent(ctx, silentParameters)
|
||||
if err == nil {
|
||||
return ar, err
|
||||
}
|
||||
authParams, err := b.AuthParams.WithTenant(onBehalfOfParams.TenantID)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
authParams.AuthorizationType = authority.ATOnBehalfOf
|
||||
authParams.Claims = onBehalfOfParams.Claims
|
||||
authParams.Scopes = onBehalfOfParams.Scopes
|
||||
authParams.UserAssertion = onBehalfOfParams.UserAssertion
|
||||
token, err := b.Token.OnBehalfOf(ctx, authParams, onBehalfOfParams.Credential)
|
||||
if err == nil {
|
||||
ar, err = b.AuthResultFromToken(ctx, authParams, token, true)
|
||||
}
|
||||
return ar, err
|
||||
}
|
||||
|
||||
func (b Client) AuthResultFromToken(ctx context.Context, authParams authority.AuthParams, token accesstokens.TokenResponse, cacheWrite bool) (AuthResult, error) {
|
||||
if !cacheWrite {
|
||||
return NewAuthResult(token, shared.Account{})
|
||||
}
|
||||
var m manager = b.manager
|
||||
if authParams.AuthorizationType == authority.ATOnBehalfOf {
|
||||
m = b.pmanager
|
||||
}
|
||||
key := token.CacheKey(authParams)
|
||||
if b.cacheAccessor != nil {
|
||||
b.cacheAccessorMu.Lock()
|
||||
defer b.cacheAccessorMu.Unlock()
|
||||
err := b.cacheAccessor.Replace(ctx, m, cache.ReplaceHints{PartitionKey: key})
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
}
|
||||
account, err := m.Write(authParams, token)
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
ar, err := NewAuthResult(token, account)
|
||||
if err == nil && b.cacheAccessor != nil {
|
||||
err = b.cacheAccessor.Export(ctx, b.manager, cache.ExportHints{PartitionKey: key})
|
||||
}
|
||||
if err != nil {
|
||||
return AuthResult{}, err
|
||||
}
|
||||
|
||||
ar.AccessToken, err = authParams.AuthnScheme.FormatAccessToken(ar.AccessToken)
|
||||
return ar, err
|
||||
}
|
||||
|
||||
func (b Client) AllAccounts(ctx context.Context) ([]shared.Account, error) {
|
||||
if b.cacheAccessor != nil {
|
||||
b.cacheAccessorMu.RLock()
|
||||
defer b.cacheAccessorMu.RUnlock()
|
||||
key := b.AuthParams.CacheKey(false)
|
||||
err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return b.manager.AllAccounts(), nil
|
||||
}
|
||||
|
||||
func (b Client) Account(ctx context.Context, homeAccountID string) (shared.Account, error) {
|
||||
if b.cacheAccessor != nil {
|
||||
b.cacheAccessorMu.RLock()
|
||||
defer b.cacheAccessorMu.RUnlock()
|
||||
authParams := b.AuthParams // This is a copy, as we don't have a pointer receiver and .AuthParams is not a pointer.
|
||||
authParams.AuthorizationType = authority.AccountByID
|
||||
authParams.HomeAccountID = homeAccountID
|
||||
key := b.AuthParams.CacheKey(false)
|
||||
err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key})
|
||||
if err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
}
|
||||
return b.manager.Account(homeAccountID), nil
|
||||
}
|
||||
|
||||
// RemoveAccount removes all the ATs, RTs and IDTs from the cache associated with this account.
|
||||
func (b Client) RemoveAccount(ctx context.Context, account shared.Account) error {
|
||||
if b.cacheAccessor == nil {
|
||||
b.manager.RemoveAccount(account, b.AuthParams.ClientID)
|
||||
return nil
|
||||
}
|
||||
b.cacheAccessorMu.Lock()
|
||||
defer b.cacheAccessorMu.Unlock()
|
||||
key := b.AuthParams.CacheKey(false)
|
||||
err := b.cacheAccessor.Replace(ctx, b.manager, cache.ReplaceHints{PartitionKey: key})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.manager.RemoveAccount(account, b.AuthParams.ClientID)
|
||||
return b.cacheAccessor.Export(ctx, b.manager, cache.ExportHints{PartitionKey: key})
|
||||
}
|
||||
Generated
Vendored
+213
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
internalTime "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
|
||||
)
|
||||
|
||||
// Contract is the JSON structure that is written to any storage medium when serializing
|
||||
// the internal cache. This design is shared between MSAL versions in many languages.
|
||||
// This cannot be changed without design that includes other SDKs.
|
||||
type Contract struct {
|
||||
AccessTokens map[string]AccessToken `json:"AccessToken,omitempty"`
|
||||
RefreshTokens map[string]accesstokens.RefreshToken `json:"RefreshToken,omitempty"`
|
||||
IDTokens map[string]IDToken `json:"IdToken,omitempty"`
|
||||
Accounts map[string]shared.Account `json:"Account,omitempty"`
|
||||
AppMetaData map[string]AppMetaData `json:"AppMetadata,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// Contract is the JSON structure that is written to any storage medium when serializing
|
||||
// the internal cache. This design is shared between MSAL versions in many languages.
|
||||
// This cannot be changed without design that includes other SDKs.
|
||||
type InMemoryContract struct {
|
||||
AccessTokensPartition map[string]map[string]AccessToken
|
||||
RefreshTokensPartition map[string]map[string]accesstokens.RefreshToken
|
||||
IDTokensPartition map[string]map[string]IDToken
|
||||
AccountsPartition map[string]map[string]shared.Account
|
||||
AppMetaData map[string]AppMetaData
|
||||
}
|
||||
|
||||
// NewContract is the constructor for Contract.
|
||||
func NewInMemoryContract() *InMemoryContract {
|
||||
return &InMemoryContract{
|
||||
AccessTokensPartition: map[string]map[string]AccessToken{},
|
||||
RefreshTokensPartition: map[string]map[string]accesstokens.RefreshToken{},
|
||||
IDTokensPartition: map[string]map[string]IDToken{},
|
||||
AccountsPartition: map[string]map[string]shared.Account{},
|
||||
AppMetaData: map[string]AppMetaData{},
|
||||
}
|
||||
}
|
||||
|
||||
// NewContract is the constructor for Contract.
|
||||
func NewContract() *Contract {
|
||||
return &Contract{
|
||||
AccessTokens: map[string]AccessToken{},
|
||||
RefreshTokens: map[string]accesstokens.RefreshToken{},
|
||||
IDTokens: map[string]IDToken{},
|
||||
Accounts: map[string]shared.Account{},
|
||||
AppMetaData: map[string]AppMetaData{},
|
||||
AdditionalFields: map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// AccessToken is the JSON representation of a MSAL access token for encoding to storage.
|
||||
type AccessToken struct {
|
||||
HomeAccountID string `json:"home_account_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
CredentialType string `json:"credential_type,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
Scopes string `json:"target,omitempty"`
|
||||
ExpiresOn internalTime.Unix `json:"expires_on,omitempty"`
|
||||
ExtendedExpiresOn internalTime.Unix `json:"extended_expires_on,omitempty"`
|
||||
CachedAt internalTime.Unix `json:"cached_at,omitempty"`
|
||||
UserAssertionHash string `json:"user_assertion_hash,omitempty"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
AuthnSchemeKeyID string `json:"keyid,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// NewAccessToken is the constructor for AccessToken.
|
||||
func NewAccessToken(homeID, env, realm, clientID string, cachedAt, expiresOn, extendedExpiresOn time.Time, scopes, token, tokenType, authnSchemeKeyID string) AccessToken {
|
||||
return AccessToken{
|
||||
HomeAccountID: homeID,
|
||||
Environment: env,
|
||||
Realm: realm,
|
||||
CredentialType: "AccessToken",
|
||||
ClientID: clientID,
|
||||
Secret: token,
|
||||
Scopes: scopes,
|
||||
CachedAt: internalTime.Unix{T: cachedAt.UTC()},
|
||||
ExpiresOn: internalTime.Unix{T: expiresOn.UTC()},
|
||||
ExtendedExpiresOn: internalTime.Unix{T: extendedExpiresOn.UTC()},
|
||||
TokenType: tokenType,
|
||||
AuthnSchemeKeyID: authnSchemeKeyID,
|
||||
}
|
||||
}
|
||||
|
||||
// Key outputs the key that can be used to uniquely look up this entry in a map.
|
||||
func (a AccessToken) Key() string {
|
||||
key := strings.Join(
|
||||
[]string{a.HomeAccountID, a.Environment, a.CredentialType, a.ClientID, a.Realm, a.Scopes},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
// add token type to key for new access tokens types. skip for bearer token type to
|
||||
// preserve fwd and back compat between a common cache and msal clients
|
||||
if !strings.EqualFold(a.TokenType, authority.AccessTokenTypeBearer) {
|
||||
key = strings.Join([]string{key, a.TokenType}, shared.CacheKeySeparator)
|
||||
}
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
// FakeValidate enables tests to fake access token validation
|
||||
var FakeValidate func(AccessToken) error
|
||||
|
||||
// Validate validates that this AccessToken can be used.
|
||||
func (a AccessToken) Validate() error {
|
||||
if FakeValidate != nil {
|
||||
return FakeValidate(a)
|
||||
}
|
||||
if a.CachedAt.T.After(time.Now()) {
|
||||
return errors.New("access token isn't valid, it was cached at a future time")
|
||||
}
|
||||
if a.ExpiresOn.T.Before(time.Now().Add(5 * time.Minute)) {
|
||||
return fmt.Errorf("access token is expired")
|
||||
}
|
||||
if a.CachedAt.T.IsZero() {
|
||||
return fmt.Errorf("access token does not have CachedAt set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IDToken is the JSON representation of an MSAL id token for encoding to storage.
|
||||
type IDToken struct {
|
||||
HomeAccountID string `json:"home_account_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
CredentialType string `json:"credential_type,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
UserAssertionHash string `json:"user_assertion_hash,omitempty"`
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// IsZero determines if IDToken is the zero value.
|
||||
func (i IDToken) IsZero() bool {
|
||||
v := reflect.ValueOf(i)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if !field.IsZero() {
|
||||
switch field.Kind() {
|
||||
case reflect.Map, reflect.Slice:
|
||||
if field.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// NewIDToken is the constructor for IDToken.
|
||||
func NewIDToken(homeID, env, realm, clientID, idToken string) IDToken {
|
||||
return IDToken{
|
||||
HomeAccountID: homeID,
|
||||
Environment: env,
|
||||
Realm: realm,
|
||||
CredentialType: "IDToken",
|
||||
ClientID: clientID,
|
||||
Secret: idToken,
|
||||
}
|
||||
}
|
||||
|
||||
// Key outputs the key that can be used to uniquely look up this entry in a map.
|
||||
func (id IDToken) Key() string {
|
||||
key := strings.Join(
|
||||
[]string{id.HomeAccountID, id.Environment, id.CredentialType, id.ClientID, id.Realm},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
// AppMetaData is the JSON representation of application metadata for encoding to storage.
|
||||
type AppMetaData struct {
|
||||
FamilyID string `json:"family_id,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// NewAppMetaData is the constructor for AppMetaData.
|
||||
func NewAppMetaData(familyID, clientID, environment string) AppMetaData {
|
||||
return AppMetaData{
|
||||
FamilyID: familyID,
|
||||
ClientID: clientID,
|
||||
Environment: environment,
|
||||
}
|
||||
}
|
||||
|
||||
// Key outputs the key that can be used to uniquely look up this entry in a map.
|
||||
func (a AppMetaData) Key() string {
|
||||
key := strings.Join(
|
||||
[]string{"AppMetaData", a.Environment, a.ClientID},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
Generated
Vendored
+442
@@ -0,0 +1,442 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
|
||||
)
|
||||
|
||||
// PartitionedManager is a partitioned in-memory cache of access tokens, accounts and meta data.
|
||||
type PartitionedManager struct {
|
||||
contract *InMemoryContract
|
||||
contractMu sync.RWMutex
|
||||
requests aadInstanceDiscoveryer // *oauth.Token
|
||||
|
||||
aadCacheMu sync.RWMutex
|
||||
aadCache map[string]authority.InstanceDiscoveryMetadata
|
||||
}
|
||||
|
||||
// NewPartitionedManager is the constructor for PartitionedManager.
|
||||
func NewPartitionedManager(requests *oauth.Client) *PartitionedManager {
|
||||
m := &PartitionedManager{requests: requests, aadCache: make(map[string]authority.InstanceDiscoveryMetadata)}
|
||||
m.contract = NewInMemoryContract()
|
||||
return m
|
||||
}
|
||||
|
||||
// Read reads a storage token from the cache if it exists.
|
||||
func (m *PartitionedManager) Read(ctx context.Context, authParameters authority.AuthParams) (TokenResponse, error) {
|
||||
tr := TokenResponse{}
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
scopes := authParameters.Scopes
|
||||
authnSchemeKeyID := authParameters.AuthnScheme.KeyID()
|
||||
tokenType := authParameters.AuthnScheme.AccessTokenType()
|
||||
|
||||
// fetch metadata if instanceDiscovery is enabled
|
||||
aliases := []string{authParameters.AuthorityInfo.Host}
|
||||
if !authParameters.AuthorityInfo.InstanceDiscoveryDisabled {
|
||||
metadata, err := m.getMetadataEntry(ctx, authParameters.AuthorityInfo)
|
||||
if err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
aliases = metadata.Aliases
|
||||
}
|
||||
|
||||
userAssertionHash := authParameters.AssertionHash()
|
||||
partitionKeyFromRequest := userAssertionHash
|
||||
|
||||
// errors returned by read* methods indicate a cache miss and are therefore non-fatal. We continue populating
|
||||
// TokenResponse fields so that e.g. lack of an ID token doesn't prevent the caller from receiving a refresh token.
|
||||
accessToken, err := m.readAccessToken(aliases, realm, clientID, userAssertionHash, scopes, partitionKeyFromRequest, tokenType, authnSchemeKeyID)
|
||||
if err == nil {
|
||||
tr.AccessToken = accessToken
|
||||
}
|
||||
idToken, err := m.readIDToken(aliases, realm, clientID, userAssertionHash, getPartitionKeyIDTokenRead(accessToken))
|
||||
if err == nil {
|
||||
tr.IDToken = idToken
|
||||
}
|
||||
|
||||
if appMetadata, err := m.readAppMetaData(aliases, clientID); err == nil {
|
||||
// we need the family ID to identify the correct refresh token, if any
|
||||
familyID := appMetadata.FamilyID
|
||||
refreshToken, err := m.readRefreshToken(aliases, familyID, clientID, userAssertionHash, partitionKeyFromRequest)
|
||||
if err == nil {
|
||||
tr.RefreshToken = refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
account, err := m.readAccount(aliases, realm, userAssertionHash, idToken.HomeAccountID)
|
||||
if err == nil {
|
||||
tr.Account = account
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// Write writes a token response to the cache and returns the account information the token is stored with.
|
||||
func (m *PartitionedManager) Write(authParameters authority.AuthParams, tokenResponse accesstokens.TokenResponse) (shared.Account, error) {
|
||||
authParameters.HomeAccountID = tokenResponse.HomeAccountID()
|
||||
homeAccountID := authParameters.HomeAccountID
|
||||
environment := authParameters.AuthorityInfo.Host
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
target := strings.Join(tokenResponse.GrantedScopes.Slice, scopeSeparator)
|
||||
userAssertionHash := authParameters.AssertionHash()
|
||||
cachedAt := time.Now()
|
||||
authnSchemeKeyID := authParameters.AuthnScheme.KeyID()
|
||||
var account shared.Account
|
||||
|
||||
if len(tokenResponse.RefreshToken) > 0 {
|
||||
refreshToken := accesstokens.NewRefreshToken(homeAccountID, environment, clientID, tokenResponse.RefreshToken, tokenResponse.FamilyID)
|
||||
if authParameters.AuthorizationType == authority.ATOnBehalfOf {
|
||||
refreshToken.UserAssertionHash = userAssertionHash
|
||||
}
|
||||
if err := m.writeRefreshToken(refreshToken, getPartitionKeyRefreshToken(refreshToken)); err != nil {
|
||||
return account, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tokenResponse.AccessToken) > 0 {
|
||||
accessToken := NewAccessToken(
|
||||
homeAccountID,
|
||||
environment,
|
||||
realm,
|
||||
clientID,
|
||||
cachedAt,
|
||||
tokenResponse.ExpiresOn.T,
|
||||
tokenResponse.ExtExpiresOn.T,
|
||||
target,
|
||||
tokenResponse.AccessToken,
|
||||
tokenResponse.TokenType,
|
||||
authnSchemeKeyID,
|
||||
)
|
||||
if authParameters.AuthorizationType == authority.ATOnBehalfOf {
|
||||
accessToken.UserAssertionHash = userAssertionHash // get Hash method on this
|
||||
}
|
||||
|
||||
// Since we have a valid access token, cache it before moving on.
|
||||
if err := accessToken.Validate(); err == nil {
|
||||
if err := m.writeAccessToken(accessToken, getPartitionKeyAccessToken(accessToken)); err != nil {
|
||||
return account, err
|
||||
}
|
||||
} else {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
}
|
||||
|
||||
idTokenJwt := tokenResponse.IDToken
|
||||
if !idTokenJwt.IsZero() {
|
||||
idToken := NewIDToken(homeAccountID, environment, realm, clientID, idTokenJwt.RawToken)
|
||||
if authParameters.AuthorizationType == authority.ATOnBehalfOf {
|
||||
idToken.UserAssertionHash = userAssertionHash
|
||||
}
|
||||
if err := m.writeIDToken(idToken, getPartitionKeyIDToken(idToken)); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
|
||||
localAccountID := idTokenJwt.LocalAccountID()
|
||||
authorityType := authParameters.AuthorityInfo.AuthorityType
|
||||
|
||||
preferredUsername := idTokenJwt.UPN
|
||||
if idTokenJwt.PreferredUsername != "" {
|
||||
preferredUsername = idTokenJwt.PreferredUsername
|
||||
}
|
||||
|
||||
account = shared.NewAccount(
|
||||
homeAccountID,
|
||||
environment,
|
||||
realm,
|
||||
localAccountID,
|
||||
authorityType,
|
||||
preferredUsername,
|
||||
)
|
||||
if authParameters.AuthorizationType == authority.ATOnBehalfOf {
|
||||
account.UserAssertionHash = userAssertionHash
|
||||
}
|
||||
if err := m.writeAccount(account, getPartitionKeyAccount(account)); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
}
|
||||
|
||||
AppMetaData := NewAppMetaData(tokenResponse.FamilyID, clientID, environment)
|
||||
|
||||
if err := m.writeAppMetaData(AppMetaData); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) getMetadataEntry(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
md, err := m.aadMetadataFromCache(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
// not in the cache, retrieve it
|
||||
md, err = m.aadMetadata(ctx, authorityInfo)
|
||||
}
|
||||
return md, err
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) aadMetadataFromCache(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
m.aadCacheMu.RLock()
|
||||
defer m.aadCacheMu.RUnlock()
|
||||
metadata, ok := m.aadCache[authorityInfo.Host]
|
||||
if ok {
|
||||
return metadata, nil
|
||||
}
|
||||
return metadata, errors.New("not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) aadMetadata(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
discoveryResponse, err := m.requests.AADInstanceDiscovery(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
return authority.InstanceDiscoveryMetadata{}, err
|
||||
}
|
||||
|
||||
m.aadCacheMu.Lock()
|
||||
defer m.aadCacheMu.Unlock()
|
||||
|
||||
for _, metadataEntry := range discoveryResponse.Metadata {
|
||||
for _, aliasedAuthority := range metadataEntry.Aliases {
|
||||
m.aadCache[aliasedAuthority] = metadataEntry
|
||||
}
|
||||
}
|
||||
if _, ok := m.aadCache[authorityInfo.Host]; !ok {
|
||||
m.aadCache[authorityInfo.Host] = authority.InstanceDiscoveryMetadata{
|
||||
PreferredNetwork: authorityInfo.Host,
|
||||
PreferredCache: authorityInfo.Host,
|
||||
}
|
||||
}
|
||||
return m.aadCache[authorityInfo.Host], nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readAccessToken(envAliases []string, realm, clientID, userAssertionHash string, scopes []string, partitionKey, tokenType, authnSchemeKeyID string) (AccessToken, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
if accessTokens, ok := m.contract.AccessTokensPartition[partitionKey]; ok {
|
||||
// TODO: linear search (over a map no less) is slow for a large number (thousands) of tokens.
|
||||
// this shows up as the dominating node in a profile. for real-world scenarios this likely isn't
|
||||
// an issue, however if it does become a problem then we know where to look.
|
||||
for _, at := range accessTokens {
|
||||
if at.Realm == realm && at.ClientID == clientID && at.UserAssertionHash == userAssertionHash {
|
||||
if at.TokenType == tokenType && at.AuthnSchemeKeyID == authnSchemeKeyID {
|
||||
if checkAlias(at.Environment, envAliases) {
|
||||
if isMatchingScopes(scopes, at.Scopes) {
|
||||
return at, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return AccessToken{}, fmt.Errorf("access token not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeAccessToken(accessToken AccessToken, partitionKey string) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
key := accessToken.Key()
|
||||
if m.contract.AccessTokensPartition[partitionKey] == nil {
|
||||
m.contract.AccessTokensPartition[partitionKey] = make(map[string]AccessToken)
|
||||
}
|
||||
m.contract.AccessTokensPartition[partitionKey][key] = accessToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func matchFamilyRefreshTokenObo(rt accesstokens.RefreshToken, userAssertionHash string, envAliases []string) bool {
|
||||
return rt.UserAssertionHash == userAssertionHash && checkAlias(rt.Environment, envAliases) && rt.FamilyID != ""
|
||||
}
|
||||
|
||||
func matchClientIDRefreshTokenObo(rt accesstokens.RefreshToken, userAssertionHash string, envAliases []string, clientID string) bool {
|
||||
return rt.UserAssertionHash == userAssertionHash && checkAlias(rt.Environment, envAliases) && rt.ClientID == clientID
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readRefreshToken(envAliases []string, familyID, clientID, userAssertionHash, partitionKey string) (accesstokens.RefreshToken, error) {
|
||||
byFamily := func(rt accesstokens.RefreshToken) bool {
|
||||
return matchFamilyRefreshTokenObo(rt, userAssertionHash, envAliases)
|
||||
}
|
||||
byClient := func(rt accesstokens.RefreshToken) bool {
|
||||
return matchClientIDRefreshTokenObo(rt, userAssertionHash, envAliases, clientID)
|
||||
}
|
||||
|
||||
var matchers []func(rt accesstokens.RefreshToken) bool
|
||||
if familyID == "" {
|
||||
matchers = []func(rt accesstokens.RefreshToken) bool{
|
||||
byClient, byFamily,
|
||||
}
|
||||
} else {
|
||||
matchers = []func(rt accesstokens.RefreshToken) bool{
|
||||
byFamily, byClient,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(keegan): All the tests here pass, but Bogdan says this is
|
||||
// more complicated. I'm opening an issue for this to have him
|
||||
// review the tests and suggest tests that would break this so
|
||||
// we can re-write against good tests. His comments as follow:
|
||||
// The algorithm is a bit more complex than this, I assume there are some tests covering everything. I would keep the order as is.
|
||||
// The algorithm is:
|
||||
// If application is NOT part of the family, search by client_ID
|
||||
// If app is part of the family or if we DO NOT KNOW if it's part of the family, search by family ID, then by client_id (we will know if an app is part of the family after the first token response).
|
||||
// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/311fe8b16e7c293462806f397e189a6aa1159769/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs#L95
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
for _, matcher := range matchers {
|
||||
for _, rt := range m.contract.RefreshTokensPartition[partitionKey] {
|
||||
if matcher(rt) {
|
||||
return rt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accesstokens.RefreshToken{}, fmt.Errorf("refresh token not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeRefreshToken(refreshToken accesstokens.RefreshToken, partitionKey string) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
key := refreshToken.Key()
|
||||
if m.contract.AccessTokensPartition[partitionKey] == nil {
|
||||
m.contract.RefreshTokensPartition[partitionKey] = make(map[string]accesstokens.RefreshToken)
|
||||
}
|
||||
m.contract.RefreshTokensPartition[partitionKey][key] = refreshToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readIDToken(envAliases []string, realm, clientID, userAssertionHash, partitionKey string) (IDToken, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
for _, idt := range m.contract.IDTokensPartition[partitionKey] {
|
||||
if idt.Realm == realm && idt.ClientID == clientID && idt.UserAssertionHash == userAssertionHash {
|
||||
if checkAlias(idt.Environment, envAliases) {
|
||||
return idt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return IDToken{}, fmt.Errorf("token not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeIDToken(idToken IDToken, partitionKey string) error {
|
||||
key := idToken.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
if m.contract.IDTokensPartition[partitionKey] == nil {
|
||||
m.contract.IDTokensPartition[partitionKey] = make(map[string]IDToken)
|
||||
}
|
||||
m.contract.IDTokensPartition[partitionKey][key] = idToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readAccount(envAliases []string, realm, UserAssertionHash, partitionKey string) (shared.Account, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
// You might ask why, if cache.Accounts is a map, we would loop through all of these instead of using a key.
|
||||
// We only use a map because the storage contract shared between all language implementations says use a map.
|
||||
// We can't change that. The other is because the keys are made using a specific "env", but here we are allowing
|
||||
// a match in multiple envs (envAlias). That means we either need to hash each possible keyand do the lookup
|
||||
// or just statically check. Since the design is to have a storage.Manager per user, the amount of keys stored
|
||||
// is really low (say 2). Each hash is more expensive than the entire iteration.
|
||||
for _, acc := range m.contract.AccountsPartition[partitionKey] {
|
||||
if checkAlias(acc.Environment, envAliases) && acc.UserAssertionHash == UserAssertionHash && acc.Realm == realm {
|
||||
return acc, nil
|
||||
}
|
||||
}
|
||||
return shared.Account{}, fmt.Errorf("account not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeAccount(account shared.Account, partitionKey string) error {
|
||||
key := account.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
if m.contract.AccountsPartition[partitionKey] == nil {
|
||||
m.contract.AccountsPartition[partitionKey] = make(map[string]shared.Account)
|
||||
}
|
||||
m.contract.AccountsPartition[partitionKey][key] = account
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) readAppMetaData(envAliases []string, clientID string) (AppMetaData, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
for _, app := range m.contract.AppMetaData {
|
||||
if checkAlias(app.Environment, envAliases) && app.ClientID == clientID {
|
||||
return app, nil
|
||||
}
|
||||
}
|
||||
return AppMetaData{}, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
func (m *PartitionedManager) writeAppMetaData(AppMetaData AppMetaData) error {
|
||||
key := AppMetaData.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.AppMetaData[key] = AppMetaData
|
||||
return nil
|
||||
}
|
||||
|
||||
// update updates the internal cache object. This is for use in tests, other uses are not
|
||||
// supported.
|
||||
func (m *PartitionedManager) update(cache *InMemoryContract) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract = cache
|
||||
}
|
||||
|
||||
// Marshal implements cache.Marshaler.
|
||||
func (m *PartitionedManager) Marshal() ([]byte, error) {
|
||||
return json.Marshal(m.contract)
|
||||
}
|
||||
|
||||
// Unmarshal implements cache.Unmarshaler.
|
||||
func (m *PartitionedManager) Unmarshal(b []byte) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
|
||||
contract := NewInMemoryContract()
|
||||
|
||||
err := json.Unmarshal(b, contract)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.contract = contract
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPartitionKeyAccessToken(item AccessToken) string {
|
||||
if item.UserAssertionHash != "" {
|
||||
return item.UserAssertionHash
|
||||
}
|
||||
return item.HomeAccountID
|
||||
}
|
||||
|
||||
func getPartitionKeyRefreshToken(item accesstokens.RefreshToken) string {
|
||||
if item.UserAssertionHash != "" {
|
||||
return item.UserAssertionHash
|
||||
}
|
||||
return item.HomeAccountID
|
||||
}
|
||||
|
||||
func getPartitionKeyIDToken(item IDToken) string {
|
||||
return item.HomeAccountID
|
||||
}
|
||||
|
||||
func getPartitionKeyAccount(item shared.Account) string {
|
||||
return item.HomeAccountID
|
||||
}
|
||||
|
||||
func getPartitionKeyIDTokenRead(item AccessToken) string {
|
||||
return item.HomeAccountID
|
||||
}
|
||||
Generated
Vendored
+583
@@ -0,0 +1,583 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package storage holds all cached token information for MSAL. This storage can be
|
||||
// augmented with third-party extensions to provide persistent storage. In that case,
|
||||
// reads and writes in upper packages will call Marshal() to take the entire in-memory
|
||||
// representation and write it to storage and Unmarshal() to update the entire in-memory
|
||||
// storage with what was in the persistent storage. The persistent storage can only be
|
||||
// accessed in this way because multiple MSAL clients written in multiple languages can
|
||||
// access the same storage and must adhere to the same method that was defined
|
||||
// previously.
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
|
||||
)
|
||||
|
||||
// aadInstanceDiscoveryer allows faking in tests.
|
||||
// It is implemented in production by ops/authority.Client
|
||||
type aadInstanceDiscoveryer interface {
|
||||
AADInstanceDiscovery(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryResponse, error)
|
||||
}
|
||||
|
||||
// TokenResponse mimics a token response that was pulled from the cache.
|
||||
type TokenResponse struct {
|
||||
RefreshToken accesstokens.RefreshToken
|
||||
IDToken IDToken // *Credential
|
||||
AccessToken AccessToken
|
||||
Account shared.Account
|
||||
}
|
||||
|
||||
// Manager is an in-memory cache of access tokens, accounts and meta data. This data is
|
||||
// updated on read/write calls. Unmarshal() replaces all data stored here with whatever
|
||||
// was given to it on each call.
|
||||
type Manager struct {
|
||||
contract *Contract
|
||||
contractMu sync.RWMutex
|
||||
requests aadInstanceDiscoveryer // *oauth.Token
|
||||
|
||||
aadCacheMu sync.RWMutex
|
||||
aadCache map[string]authority.InstanceDiscoveryMetadata
|
||||
}
|
||||
|
||||
// New is the constructor for Manager.
|
||||
func New(requests *oauth.Client) *Manager {
|
||||
m := &Manager{requests: requests, aadCache: make(map[string]authority.InstanceDiscoveryMetadata)}
|
||||
m.contract = NewContract()
|
||||
return m
|
||||
}
|
||||
|
||||
func checkAlias(alias string, aliases []string) bool {
|
||||
for _, v := range aliases {
|
||||
if alias == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isMatchingScopes(scopesOne []string, scopesTwo string) bool {
|
||||
newScopesTwo := strings.Split(scopesTwo, scopeSeparator)
|
||||
scopeCounter := 0
|
||||
for _, scope := range scopesOne {
|
||||
for _, otherScope := range newScopesTwo {
|
||||
if strings.EqualFold(scope, otherScope) {
|
||||
scopeCounter++
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return scopeCounter == len(scopesOne)
|
||||
}
|
||||
|
||||
// needsUpgrade returns true if the given key follows the v1.0 schema i.e.,
|
||||
// it contains an uppercase character (v1.1+ keys are all lowercase)
|
||||
func needsUpgrade(key string) bool {
|
||||
for _, r := range key {
|
||||
if 'A' <= r && r <= 'Z' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// upgrade a v1.0 cache item by adding a v1.1+ item having the same value and deleting
|
||||
// the v1.0 item. Callers must hold an exclusive lock on m.
|
||||
func upgrade[T any](m map[string]T, k string) T {
|
||||
v1_1Key := strings.ToLower(k)
|
||||
v, ok := m[k]
|
||||
if !ok {
|
||||
// another goroutine did the upgrade while this one was waiting for the write lock
|
||||
return m[v1_1Key]
|
||||
}
|
||||
if v2, ok := m[v1_1Key]; ok {
|
||||
// cache has an equivalent v1.1+ item, which we prefer because we know it was added
|
||||
// by a newer version of the module and is therefore more likely to remain valid.
|
||||
// The v1.0 item may have expired because only v1.0 or earlier would update it.
|
||||
v = v2
|
||||
} else {
|
||||
// add an equivalent item according to the v1.1 schema
|
||||
m[v1_1Key] = v
|
||||
}
|
||||
delete(m, k)
|
||||
return v
|
||||
}
|
||||
|
||||
// Read reads a storage token from the cache if it exists.
|
||||
func (m *Manager) Read(ctx context.Context, authParameters authority.AuthParams) (TokenResponse, error) {
|
||||
tr := TokenResponse{}
|
||||
homeAccountID := authParameters.HomeAccountID
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
scopes := authParameters.Scopes
|
||||
authnSchemeKeyID := authParameters.AuthnScheme.KeyID()
|
||||
tokenType := authParameters.AuthnScheme.AccessTokenType()
|
||||
|
||||
// fetch metadata if instanceDiscovery is enabled
|
||||
aliases := []string{authParameters.AuthorityInfo.Host}
|
||||
if !authParameters.AuthorityInfo.InstanceDiscoveryDisabled {
|
||||
metadata, err := m.getMetadataEntry(ctx, authParameters.AuthorityInfo)
|
||||
if err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
aliases = metadata.Aliases
|
||||
}
|
||||
|
||||
accessToken := m.readAccessToken(homeAccountID, aliases, realm, clientID, scopes, tokenType, authnSchemeKeyID)
|
||||
tr.AccessToken = accessToken
|
||||
|
||||
if homeAccountID == "" {
|
||||
// caller didn't specify a user, so there's no reason to search for an ID or refresh token
|
||||
return tr, nil
|
||||
}
|
||||
// errors returned by read* methods indicate a cache miss and are therefore non-fatal. We continue populating
|
||||
// TokenResponse fields so that e.g. lack of an ID token doesn't prevent the caller from receiving a refresh token.
|
||||
idToken, err := m.readIDToken(homeAccountID, aliases, realm, clientID)
|
||||
if err == nil {
|
||||
tr.IDToken = idToken
|
||||
}
|
||||
|
||||
if appMetadata, err := m.readAppMetaData(aliases, clientID); err == nil {
|
||||
// we need the family ID to identify the correct refresh token, if any
|
||||
familyID := appMetadata.FamilyID
|
||||
refreshToken, err := m.readRefreshToken(homeAccountID, aliases, familyID, clientID)
|
||||
if err == nil {
|
||||
tr.RefreshToken = refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
account, err := m.readAccount(homeAccountID, aliases, realm)
|
||||
if err == nil {
|
||||
tr.Account = account
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
const scopeSeparator = " "
|
||||
|
||||
// Write writes a token response to the cache and returns the account information the token is stored with.
|
||||
func (m *Manager) Write(authParameters authority.AuthParams, tokenResponse accesstokens.TokenResponse) (shared.Account, error) {
|
||||
homeAccountID := tokenResponse.HomeAccountID()
|
||||
environment := authParameters.AuthorityInfo.Host
|
||||
realm := authParameters.AuthorityInfo.Tenant
|
||||
clientID := authParameters.ClientID
|
||||
target := strings.Join(tokenResponse.GrantedScopes.Slice, scopeSeparator)
|
||||
cachedAt := time.Now()
|
||||
authnSchemeKeyID := authParameters.AuthnScheme.KeyID()
|
||||
|
||||
var account shared.Account
|
||||
|
||||
if len(tokenResponse.RefreshToken) > 0 {
|
||||
refreshToken := accesstokens.NewRefreshToken(homeAccountID, environment, clientID, tokenResponse.RefreshToken, tokenResponse.FamilyID)
|
||||
if err := m.writeRefreshToken(refreshToken); err != nil {
|
||||
return account, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tokenResponse.AccessToken) > 0 {
|
||||
accessToken := NewAccessToken(
|
||||
homeAccountID,
|
||||
environment,
|
||||
realm,
|
||||
clientID,
|
||||
cachedAt,
|
||||
tokenResponse.ExpiresOn.T,
|
||||
tokenResponse.ExtExpiresOn.T,
|
||||
target,
|
||||
tokenResponse.AccessToken,
|
||||
tokenResponse.TokenType,
|
||||
authnSchemeKeyID,
|
||||
)
|
||||
|
||||
// Since we have a valid access token, cache it before moving on.
|
||||
if err := accessToken.Validate(); err == nil {
|
||||
if err := m.writeAccessToken(accessToken); err != nil {
|
||||
return account, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idTokenJwt := tokenResponse.IDToken
|
||||
if !idTokenJwt.IsZero() {
|
||||
idToken := NewIDToken(homeAccountID, environment, realm, clientID, idTokenJwt.RawToken)
|
||||
if err := m.writeIDToken(idToken); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
|
||||
localAccountID := idTokenJwt.LocalAccountID()
|
||||
authorityType := authParameters.AuthorityInfo.AuthorityType
|
||||
|
||||
preferredUsername := idTokenJwt.UPN
|
||||
if idTokenJwt.PreferredUsername != "" {
|
||||
preferredUsername = idTokenJwt.PreferredUsername
|
||||
}
|
||||
|
||||
account = shared.NewAccount(
|
||||
homeAccountID,
|
||||
environment,
|
||||
realm,
|
||||
localAccountID,
|
||||
authorityType,
|
||||
preferredUsername,
|
||||
)
|
||||
if err := m.writeAccount(account); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
}
|
||||
|
||||
AppMetaData := NewAppMetaData(tokenResponse.FamilyID, clientID, environment)
|
||||
|
||||
if err := m.writeAppMetaData(AppMetaData); err != nil {
|
||||
return shared.Account{}, err
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getMetadataEntry(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
md, err := m.aadMetadataFromCache(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
// not in the cache, retrieve it
|
||||
md, err = m.aadMetadata(ctx, authorityInfo)
|
||||
}
|
||||
return md, err
|
||||
}
|
||||
|
||||
func (m *Manager) aadMetadataFromCache(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
m.aadCacheMu.RLock()
|
||||
defer m.aadCacheMu.RUnlock()
|
||||
metadata, ok := m.aadCache[authorityInfo.Host]
|
||||
if ok {
|
||||
return metadata, nil
|
||||
}
|
||||
return metadata, errors.New("not found")
|
||||
}
|
||||
|
||||
func (m *Manager) aadMetadata(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryMetadata, error) {
|
||||
m.aadCacheMu.Lock()
|
||||
defer m.aadCacheMu.Unlock()
|
||||
discoveryResponse, err := m.requests.AADInstanceDiscovery(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
return authority.InstanceDiscoveryMetadata{}, err
|
||||
}
|
||||
|
||||
for _, metadataEntry := range discoveryResponse.Metadata {
|
||||
for _, aliasedAuthority := range metadataEntry.Aliases {
|
||||
m.aadCache[aliasedAuthority] = metadataEntry
|
||||
}
|
||||
}
|
||||
if _, ok := m.aadCache[authorityInfo.Host]; !ok {
|
||||
m.aadCache[authorityInfo.Host] = authority.InstanceDiscoveryMetadata{
|
||||
PreferredNetwork: authorityInfo.Host,
|
||||
PreferredCache: authorityInfo.Host,
|
||||
}
|
||||
}
|
||||
return m.aadCache[authorityInfo.Host], nil
|
||||
}
|
||||
|
||||
func (m *Manager) readAccessToken(homeID string, envAliases []string, realm, clientID string, scopes []string, tokenType, authnSchemeKeyID string) AccessToken {
|
||||
m.contractMu.RLock()
|
||||
// TODO: linear search (over a map no less) is slow for a large number (thousands) of tokens.
|
||||
// this shows up as the dominating node in a profile. for real-world scenarios this likely isn't
|
||||
// an issue, however if it does become a problem then we know where to look.
|
||||
for k, at := range m.contract.AccessTokens {
|
||||
if at.HomeAccountID == homeID && at.Realm == realm && at.ClientID == clientID {
|
||||
if (strings.EqualFold(at.TokenType, tokenType) && at.AuthnSchemeKeyID == authnSchemeKeyID) || (at.TokenType == "" && (tokenType == "" || tokenType == "Bearer")) {
|
||||
if checkAlias(at.Environment, envAliases) && isMatchingScopes(scopes, at.Scopes) {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
at = upgrade(m.contract.AccessTokens, k)
|
||||
}
|
||||
return at
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.contractMu.RUnlock()
|
||||
return AccessToken{}
|
||||
}
|
||||
|
||||
func (m *Manager) writeAccessToken(accessToken AccessToken) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
key := accessToken.Key()
|
||||
m.contract.AccessTokens[key] = accessToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) readRefreshToken(homeID string, envAliases []string, familyID, clientID string) (accesstokens.RefreshToken, error) {
|
||||
byFamily := func(rt accesstokens.RefreshToken) bool {
|
||||
return matchFamilyRefreshToken(rt, homeID, envAliases)
|
||||
}
|
||||
byClient := func(rt accesstokens.RefreshToken) bool {
|
||||
return matchClientIDRefreshToken(rt, homeID, envAliases, clientID)
|
||||
}
|
||||
|
||||
var matchers []func(rt accesstokens.RefreshToken) bool
|
||||
if familyID == "" {
|
||||
matchers = []func(rt accesstokens.RefreshToken) bool{
|
||||
byClient, byFamily,
|
||||
}
|
||||
} else {
|
||||
matchers = []func(rt accesstokens.RefreshToken) bool{
|
||||
byFamily, byClient,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(keegan): All the tests here pass, but Bogdan says this is
|
||||
// more complicated. I'm opening an issue for this to have him
|
||||
// review the tests and suggest tests that would break this so
|
||||
// we can re-write against good tests. His comments as follow:
|
||||
// The algorithm is a bit more complex than this, I assume there are some tests covering everything. I would keep the order as is.
|
||||
// The algorithm is:
|
||||
// If application is NOT part of the family, search by client_ID
|
||||
// If app is part of the family or if we DO NOT KNOW if it's part of the family, search by family ID, then by client_id (we will know if an app is part of the family after the first token response).
|
||||
// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/311fe8b16e7c293462806f397e189a6aa1159769/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs#L95
|
||||
m.contractMu.RLock()
|
||||
for _, matcher := range matchers {
|
||||
for k, rt := range m.contract.RefreshTokens {
|
||||
if matcher(rt) {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
rt = upgrade(m.contract.RefreshTokens, k)
|
||||
}
|
||||
return rt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.contractMu.RUnlock()
|
||||
return accesstokens.RefreshToken{}, fmt.Errorf("refresh token not found")
|
||||
}
|
||||
|
||||
func matchFamilyRefreshToken(rt accesstokens.RefreshToken, homeID string, envAliases []string) bool {
|
||||
return rt.HomeAccountID == homeID && checkAlias(rt.Environment, envAliases) && rt.FamilyID != ""
|
||||
}
|
||||
|
||||
func matchClientIDRefreshToken(rt accesstokens.RefreshToken, homeID string, envAliases []string, clientID string) bool {
|
||||
return rt.HomeAccountID == homeID && checkAlias(rt.Environment, envAliases) && rt.ClientID == clientID
|
||||
}
|
||||
|
||||
func (m *Manager) writeRefreshToken(refreshToken accesstokens.RefreshToken) error {
|
||||
key := refreshToken.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.RefreshTokens[key] = refreshToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) readIDToken(homeID string, envAliases []string, realm, clientID string) (IDToken, error) {
|
||||
m.contractMu.RLock()
|
||||
for k, idt := range m.contract.IDTokens {
|
||||
if idt.HomeAccountID == homeID && idt.Realm == realm && idt.ClientID == clientID {
|
||||
if checkAlias(idt.Environment, envAliases) {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
idt = upgrade(m.contract.IDTokens, k)
|
||||
}
|
||||
return idt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
m.contractMu.RUnlock()
|
||||
return IDToken{}, fmt.Errorf("token not found")
|
||||
}
|
||||
|
||||
func (m *Manager) writeIDToken(idToken IDToken) error {
|
||||
key := idToken.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.IDTokens[key] = idToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) AllAccounts() []shared.Account {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
var accounts []shared.Account
|
||||
for _, v := range m.contract.Accounts {
|
||||
accounts = append(accounts, v)
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
||||
func (m *Manager) Account(homeAccountID string) shared.Account {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
|
||||
for _, v := range m.contract.Accounts {
|
||||
if v.HomeAccountID == homeAccountID {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return shared.Account{}
|
||||
}
|
||||
|
||||
func (m *Manager) readAccount(homeAccountID string, envAliases []string, realm string) (shared.Account, error) {
|
||||
m.contractMu.RLock()
|
||||
|
||||
// You might ask why, if cache.Accounts is a map, we would loop through all of these instead of using a key.
|
||||
// We only use a map because the storage contract shared between all language implementations says use a map.
|
||||
// We can't change that. The other is because the keys are made using a specific "env", but here we are allowing
|
||||
// a match in multiple envs (envAlias). That means we either need to hash each possible keyand do the lookup
|
||||
// or just statically check. Since the design is to have a storage.Manager per user, the amount of keys stored
|
||||
// is really low (say 2). Each hash is more expensive than the entire iteration.
|
||||
for k, acc := range m.contract.Accounts {
|
||||
if acc.HomeAccountID == homeAccountID && checkAlias(acc.Environment, envAliases) && acc.Realm == realm {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
acc = upgrade(m.contract.Accounts, k)
|
||||
}
|
||||
return acc, nil
|
||||
}
|
||||
}
|
||||
m.contractMu.RUnlock()
|
||||
return shared.Account{}, fmt.Errorf("account not found")
|
||||
}
|
||||
|
||||
func (m *Manager) writeAccount(account shared.Account) error {
|
||||
key := account.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.Accounts[key] = account
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) readAppMetaData(envAliases []string, clientID string) (AppMetaData, error) {
|
||||
m.contractMu.RLock()
|
||||
for k, app := range m.contract.AppMetaData {
|
||||
if checkAlias(app.Environment, envAliases) && app.ClientID == clientID {
|
||||
m.contractMu.RUnlock()
|
||||
if needsUpgrade(k) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
app = upgrade(m.contract.AppMetaData, k)
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
}
|
||||
m.contractMu.RUnlock()
|
||||
return AppMetaData{}, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
func (m *Manager) writeAppMetaData(AppMetaData AppMetaData) error {
|
||||
key := AppMetaData.Key()
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract.AppMetaData[key] = AppMetaData
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAccount removes all the associated ATs, RTs and IDTs from the cache associated with this account.
|
||||
func (m *Manager) RemoveAccount(account shared.Account, clientID string) {
|
||||
m.removeRefreshTokens(account.HomeAccountID, account.Environment, clientID)
|
||||
m.removeAccessTokens(account.HomeAccountID, account.Environment)
|
||||
m.removeIDTokens(account.HomeAccountID, account.Environment)
|
||||
m.removeAccounts(account.HomeAccountID, account.Environment)
|
||||
}
|
||||
|
||||
func (m *Manager) removeRefreshTokens(homeID string, env string, clientID string) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
for key, rt := range m.contract.RefreshTokens {
|
||||
// Check for RTs associated with the account.
|
||||
if rt.HomeAccountID == homeID && rt.Environment == env {
|
||||
// Do RT's app ownership check as a precaution, in case family apps
|
||||
// and 3rd-party apps share same token cache, although they should not.
|
||||
if rt.ClientID == clientID || rt.FamilyID != "" {
|
||||
delete(m.contract.RefreshTokens, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) removeAccessTokens(homeID string, env string) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
for key, at := range m.contract.AccessTokens {
|
||||
// Remove AT's associated with the account
|
||||
if at.HomeAccountID == homeID && at.Environment == env {
|
||||
// # To avoid the complexity of locating sibling family app's AT, we skip AT's app ownership check.
|
||||
// It means ATs for other apps will also be removed, it is OK because:
|
||||
// non-family apps are not supposed to share token cache to begin with;
|
||||
// Even if it happens, we keep other app's RT already, so SSO still works.
|
||||
delete(m.contract.AccessTokens, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) removeIDTokens(homeID string, env string) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
for key, idt := range m.contract.IDTokens {
|
||||
// Remove ID tokens associated with the account.
|
||||
if idt.HomeAccountID == homeID && idt.Environment == env {
|
||||
delete(m.contract.IDTokens, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) removeAccounts(homeID string, env string) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
for key, acc := range m.contract.Accounts {
|
||||
// Remove the specified account.
|
||||
if acc.HomeAccountID == homeID && acc.Environment == env {
|
||||
delete(m.contract.Accounts, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update updates the internal cache object. This is for use in tests, other uses are not
|
||||
// supported.
|
||||
func (m *Manager) update(cache *Contract) {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
m.contract = cache
|
||||
}
|
||||
|
||||
// Marshal implements cache.Marshaler.
|
||||
func (m *Manager) Marshal() ([]byte, error) {
|
||||
m.contractMu.RLock()
|
||||
defer m.contractMu.RUnlock()
|
||||
return json.Marshal(m.contract)
|
||||
}
|
||||
|
||||
// Unmarshal implements cache.Unmarshaler.
|
||||
func (m *Manager) Unmarshal(b []byte) error {
|
||||
m.contractMu.Lock()
|
||||
defer m.contractMu.Unlock()
|
||||
|
||||
contract := NewContract()
|
||||
|
||||
err := json.Unmarshal(b, contract)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.contract = contract
|
||||
|
||||
return nil
|
||||
}
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// package exported contains internal types that are re-exported from a public package
|
||||
package exported
|
||||
|
||||
// AssertionRequestOptions has information required to generate a client assertion
|
||||
type AssertionRequestOptions struct {
|
||||
// ClientID identifies the application for which an assertion is requested. Used as the assertion's "iss" and "sub" claims.
|
||||
ClientID string
|
||||
|
||||
// TokenEndpoint is the intended token endpoint. Used as the assertion's "aud" claim.
|
||||
TokenEndpoint string
|
||||
}
|
||||
|
||||
// TokenProviderParameters is the authentication parameters passed to token providers
|
||||
type TokenProviderParameters struct {
|
||||
// Claims contains any additional claims requested for the token
|
||||
Claims string
|
||||
// CorrelationID of the authentication request
|
||||
CorrelationID string
|
||||
// Scopes requested for the token
|
||||
Scopes []string
|
||||
// TenantID identifies the tenant in which to authenticate
|
||||
TenantID string
|
||||
}
|
||||
|
||||
// TokenProviderResult is the authentication result returned by custom token providers
|
||||
type TokenProviderResult struct {
|
||||
// AccessToken is the requested token
|
||||
AccessToken string
|
||||
// ExpiresInSeconds is the lifetime of the token in seconds
|
||||
ExpiresInSeconds int
|
||||
}
|
||||
Generated
Vendored
+140
@@ -0,0 +1,140 @@
|
||||
# JSON Package Design
|
||||
Author: John Doak(jdoak@microsoft.com)
|
||||
|
||||
## Why?
|
||||
|
||||
This project needs a special type of marshal/unmarshal not directly supported
|
||||
by the encoding/json package.
|
||||
|
||||
The need revolves around a few key wants/needs:
|
||||
- unmarshal and marshal structs representing JSON messages
|
||||
- fields in the messgage not in the struct must be maintained when unmarshalled
|
||||
- those same fields must be marshalled back when encoded again
|
||||
|
||||
The initial version used map[string]interface{} to put in the keys that
|
||||
were known and then any other keys were put into a field called AdditionalFields.
|
||||
|
||||
This has a few negatives:
|
||||
- Dual marshaling/unmarshalling is required
|
||||
- Adding a struct field requires manually adding a key by name to be encoded/decoded from the map (which is a loosely coupled construct), which can lead to bugs that aren't detected or have bad side effects
|
||||
- Tests can become quickly disconnected if those keys aren't put
|
||||
in tests as well. So you think you have support working, but you
|
||||
don't. Existing tests were found that didn't test the marshalling output.
|
||||
- There is no enforcement that if AdditionalFields is required on one struct, it should be on all containers
|
||||
that don't have custom marshal/unmarshal.
|
||||
|
||||
This package aims to support our needs by providing custom Marshal()/Unmarshal() functions.
|
||||
|
||||
This prevents all the negatives in the initial solution listed above. However, it does add its own negative:
|
||||
- Custom encoding/decoding via reflection is messy (as can be seen in encoding/json itself)
|
||||
|
||||
Go proverb: Reflection is never clear
|
||||
Suggested reading: https://blog.golang.org/laws-of-reflection
|
||||
|
||||
## Important design decisions
|
||||
|
||||
- We don't want to understand all JSON decoding rules
|
||||
- We don't want to deal with all the quoting, commas, etc on decode
|
||||
- Need support for json.Marshaler/Unmarshaler, so we can support types like time.Time
|
||||
- If struct does not implement json.Unmarshaler, it must have AdditionalFields defined
|
||||
- We only support root level objects that are \*struct or struct
|
||||
|
||||
To faciliate these goals, we will utilize the json.Encoder and json.Decoder.
|
||||
They provide streaming processing (efficient) and return errors on bad JSON.
|
||||
|
||||
Support for json.Marshaler/Unmarshaler allows for us to use non-basic types
|
||||
that must be specially encoded/decoded (like time.Time objects).
|
||||
|
||||
We don't support types that can't customer unmarshal or have AdditionalFields
|
||||
in order to prevent future devs from forgetting that important field and
|
||||
generating bad return values.
|
||||
|
||||
Support for root level objects of \*struct or struct simply acknowledges the
|
||||
fact that this is designed only for the purposes listed in the Introduction.
|
||||
Outside that (like encoding a lone number) should be done with the
|
||||
regular json package (as it will not have additional fields).
|
||||
|
||||
We don't support a few things on json supported reference types and structs:
|
||||
- \*map: no need for pointers to maps
|
||||
- \*slice: no need for pointers to slices
|
||||
- any further pointers on struct after \*struct
|
||||
|
||||
There should never be a need for this in Go.
|
||||
|
||||
## Design
|
||||
|
||||
## State Machines
|
||||
|
||||
This uses state machine designs that based upon the Rob Pike talk on
|
||||
lexers and parsers: https://www.youtube.com/watch?v=HxaD_trXwRE
|
||||
|
||||
This is the most common pattern for state machines in Go and
|
||||
the model to follow closesly when dealing with streaming
|
||||
processing of textual data.
|
||||
|
||||
Our state machines are based on the type:
|
||||
```go
|
||||
type stateFn func() (stateFn, error)
|
||||
```
|
||||
|
||||
The state machine itself is simply a struct that has methods that
|
||||
satisfy stateFn.
|
||||
|
||||
Our state machines have a few standard calls
|
||||
- run(): runs the state machine
|
||||
- start(): always the first stateFn to be called
|
||||
|
||||
All state machines have the following logic:
|
||||
* run() is called
|
||||
* start() is called and returns the next stateFn or error
|
||||
* stateFn is called
|
||||
- If returned stateFn(next state) is non-nil, call it
|
||||
- If error is non-nil, run() returns the error
|
||||
- If stateFn == nil and err == nil, run() return err == nil
|
||||
|
||||
## Supporting types
|
||||
|
||||
Marshalling/Unmarshalling must support(within top level struct):
|
||||
- struct
|
||||
- \*struct
|
||||
- []struct
|
||||
- []\*struct
|
||||
- []map[string]structContainer
|
||||
- [][]structContainer
|
||||
|
||||
**Term note:** structContainer == type that has a struct or \*struct inside it
|
||||
|
||||
We specifically do not support []interface or map[string]interface
|
||||
where the interface value would hold some value with a struct in it.
|
||||
|
||||
Those will still marshal/unmarshal, but without support for
|
||||
AdditionalFields.
|
||||
|
||||
## Marshalling
|
||||
|
||||
The marshalling design will be based around a statemachine design.
|
||||
|
||||
The basic logic is as follows:
|
||||
|
||||
* If struct has custom marshaller, call it and return
|
||||
* If struct has field "AdditionalFields", it must be a map[string]interface{}
|
||||
* If struct does not have "AdditionalFields", give an error
|
||||
* Get struct tag detailing json names to go names, create mapping
|
||||
* For each public field name
|
||||
- Write field name out
|
||||
- If field value is a struct, recursively call our state machine
|
||||
- Otherwise, use the json.Encoder to write out the value
|
||||
|
||||
## Unmarshalling
|
||||
|
||||
The unmarshalling desin is also based around a statemachine design. The
|
||||
basic logic is as follows:
|
||||
|
||||
* If struct has custom marhaller, call it
|
||||
* If struct has field "AdditionalFields", it must be a map[string]interface{}
|
||||
* Get struct tag detailing json names to go names, create mapping
|
||||
* For each key found
|
||||
- If key exists,
|
||||
- If value is basic type, extract value into struct field using Decoder
|
||||
- If value is struct type, recursively call statemachine
|
||||
- If key doesn't exist, add it to AdditionalFields if it exists using Decoder
|
||||
backend/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go
Generated
Vendored
+166
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package json provide functions for marshalling an unmarshalling types to JSON. These functions are meant to
|
||||
// be utilized inside of structs that implement json.Unmarshaler and json.Marshaler interfaces.
|
||||
// This package provides the additional functionality of writing fields that are not in the struct when marshalling
|
||||
// to a field called AdditionalFields if that field exists and is a map[string]interface{}.
|
||||
// When marshalling, if the struct has all the same prerequisites, it will uses the keys in AdditionalFields as
|
||||
// extra fields. This package uses encoding/json underneath.
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const addField = "AdditionalFields"
|
||||
|
||||
var (
|
||||
leftBrace = []byte("{")[0]
|
||||
rightBrace = []byte("}")[0]
|
||||
comma = []byte(",")[0]
|
||||
leftParen = []byte("[")[0]
|
||||
rightParen = []byte("]")[0]
|
||||
)
|
||||
|
||||
var mapStrInterType = reflect.TypeOf(map[string]interface{}{})
|
||||
|
||||
// stateFn defines a state machine function. This will be used in all state
|
||||
// machines in this package.
|
||||
type stateFn func() (stateFn, error)
|
||||
|
||||
// Marshal is used to marshal a type into its JSON representation. It
|
||||
// wraps the stdlib calls in order to marshal a struct or *struct so
|
||||
// that a field called "AdditionalFields" of type map[string]interface{}
|
||||
// with "-" used inside struct tag `json:"-"` can be marshalled as if
|
||||
// they were fields within the struct.
|
||||
func Marshal(i interface{}) ([]byte, error) {
|
||||
buff := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buff)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", "")
|
||||
|
||||
v := reflect.ValueOf(i)
|
||||
if v.Kind() != reflect.Ptr && v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
err := marshalStruct(v, &buff, enc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals a []byte representing JSON into i, which must be a *struct. In addition, if the struct has
|
||||
// a field called AdditionalFields of type map[string]interface{}, JSON data representing fields not in the struct
|
||||
// will be written as key/value pairs to AdditionalFields.
|
||||
func Unmarshal(b []byte, i interface{}) error {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
jdec := json.NewDecoder(bytes.NewBuffer(b))
|
||||
jdec.UseNumber()
|
||||
return unmarshalStruct(jdec, i)
|
||||
}
|
||||
|
||||
// MarshalRaw marshals i into a json.RawMessage. If I cannot be marshalled,
|
||||
// this will panic. This is exposed to help test AdditionalField values
|
||||
// which are stored as json.RawMessage.
|
||||
func MarshalRaw(i interface{}) json.RawMessage {
|
||||
b, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return json.RawMessage(b)
|
||||
}
|
||||
|
||||
// isDelim simply tests to see if a json.Token is a delimeter.
|
||||
func isDelim(got json.Token) bool {
|
||||
switch got.(type) {
|
||||
case json.Delim:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// delimIs tests got to see if it is want.
|
||||
func delimIs(got json.Token, want rune) bool {
|
||||
switch v := got.(type) {
|
||||
case json.Delim:
|
||||
if v == json.Delim(want) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasMarshalJSON will determine if the value or a pointer to this value has
|
||||
// the MarshalJSON method.
|
||||
func hasMarshalJSON(v reflect.Value) bool {
|
||||
ok := false
|
||||
if _, ok = v.Interface().(json.Marshaler); !ok {
|
||||
var i any
|
||||
if v.Kind() == reflect.Ptr {
|
||||
i = v.Elem().Interface()
|
||||
} else if v.CanAddr() {
|
||||
i = v.Addr().Interface()
|
||||
}
|
||||
_, ok = i.(json.Marshaler)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// callMarshalJSON will call MarshalJSON() method on the value or a pointer to this value.
|
||||
// This will panic if the method is not defined.
|
||||
func callMarshalJSON(v reflect.Value) ([]byte, error) {
|
||||
if marsh, ok := v.Interface().(json.Marshaler); ok {
|
||||
return marsh.MarshalJSON()
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if marsh, ok := v.Elem().Interface().(json.Marshaler); ok {
|
||||
return marsh.MarshalJSON()
|
||||
}
|
||||
} else {
|
||||
if v.CanAddr() {
|
||||
if marsh, ok := v.Addr().Interface().(json.Marshaler); ok {
|
||||
return marsh.MarshalJSON()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("callMarshalJSON called on type %T that does not have MarshalJSON defined", v.Interface()))
|
||||
}
|
||||
|
||||
// hasUnmarshalJSON will determine if the value or a pointer to this value has
|
||||
// the UnmarshalJSON method.
|
||||
func hasUnmarshalJSON(v reflect.Value) bool {
|
||||
// You can't unmarshal on a non-pointer type.
|
||||
if v.Kind() != reflect.Ptr {
|
||||
if !v.CanAddr() {
|
||||
return false
|
||||
}
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
_, ok := v.Interface().(json.Unmarshaler)
|
||||
return ok
|
||||
}
|
||||
|
||||
// hasOmitEmpty indicates if the field has instructed us to not output
|
||||
// the field if omitempty is set on the tag. tag is the string
|
||||
// returned by reflect.StructField.Tag().Get().
|
||||
func hasOmitEmpty(tag string) bool {
|
||||
sl := strings.Split(tag, ",")
|
||||
for _, str := range sl {
|
||||
if str == "omitempty" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Generated
Vendored
+333
@@ -0,0 +1,333 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// unmarshalMap unmarshal's a map.
|
||||
func unmarshalMap(dec *json.Decoder, m reflect.Value) error {
|
||||
if m.Kind() != reflect.Ptr || m.Elem().Kind() != reflect.Map {
|
||||
panic("unmarshalMap called on non-*map value")
|
||||
}
|
||||
mapValueType := m.Elem().Type().Elem()
|
||||
walk := mapWalk{dec: dec, m: m, valueType: mapValueType}
|
||||
if err := walk.run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type mapWalk struct {
|
||||
dec *json.Decoder
|
||||
key string
|
||||
m reflect.Value
|
||||
valueType reflect.Type
|
||||
}
|
||||
|
||||
// run runs our decoder state machine.
|
||||
func (m *mapWalk) run() error {
|
||||
var state = m.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mapWalk) start() (stateFn, error) {
|
||||
// maps can have custom unmarshaler's.
|
||||
if hasUnmarshalJSON(m.m) {
|
||||
err := m.dec.Decode(m.m.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We only want to use this if the map value is:
|
||||
// *struct/struct/map/slice
|
||||
// otherwise use standard decode
|
||||
t, _ := m.valueBaseType()
|
||||
switch t.Kind() {
|
||||
case reflect.Struct, reflect.Map, reflect.Slice:
|
||||
delim, err := m.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// This indicates the value was set to JSON null.
|
||||
if delim == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if !delimIs(delim, '{') {
|
||||
return nil, fmt.Errorf("Unmarshal expected opening {, received %v", delim)
|
||||
}
|
||||
return m.next, nil
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("do not support maps with values of '**type' or '*reference")
|
||||
}
|
||||
|
||||
// This is a basic map type, so just use Decode().
|
||||
if err := m.dec.Decode(m.m.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mapWalk) next() (stateFn, error) {
|
||||
if m.dec.More() {
|
||||
key, err := m.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.key = key.(string)
|
||||
return m.storeValue, nil
|
||||
}
|
||||
// No more entries, so remove final }.
|
||||
_, err := m.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mapWalk) storeValue() (stateFn, error) {
|
||||
v := m.valueType
|
||||
for {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
v = v.Elem()
|
||||
continue
|
||||
case reflect.Struct:
|
||||
return m.storeStruct, nil
|
||||
case reflect.Map:
|
||||
return m.storeMap, nil
|
||||
case reflect.Slice:
|
||||
return m.storeSlice, nil
|
||||
}
|
||||
return nil, fmt.Errorf("bug: mapWalk.storeValue() called on unsupported type: %v", v.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mapWalk) storeStruct() (stateFn, error) {
|
||||
v := newValue(m.valueType)
|
||||
if err := unmarshalStruct(m.dec, v.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.valueType.Kind() == reflect.Ptr {
|
||||
m.m.Elem().SetMapIndex(reflect.ValueOf(m.key), v)
|
||||
return m.next, nil
|
||||
}
|
||||
m.m.Elem().SetMapIndex(reflect.ValueOf(m.key), v.Elem())
|
||||
|
||||
return m.next, nil
|
||||
}
|
||||
|
||||
func (m *mapWalk) storeMap() (stateFn, error) {
|
||||
v := reflect.MakeMap(m.valueType)
|
||||
ptr := newValue(v.Type())
|
||||
ptr.Elem().Set(v)
|
||||
if err := unmarshalMap(m.dec, ptr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.m.Elem().SetMapIndex(reflect.ValueOf(m.key), v)
|
||||
|
||||
return m.next, nil
|
||||
}
|
||||
|
||||
func (m *mapWalk) storeSlice() (stateFn, error) {
|
||||
v := newValue(m.valueType)
|
||||
if err := unmarshalSlice(m.dec, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.m.Elem().SetMapIndex(reflect.ValueOf(m.key), v.Elem())
|
||||
|
||||
return m.next, nil
|
||||
}
|
||||
|
||||
// valueType returns the underlying Type. So a *struct would yield
|
||||
// struct, etc...
|
||||
func (m *mapWalk) valueBaseType() (reflect.Type, bool) {
|
||||
ptr := false
|
||||
v := m.valueType
|
||||
if v.Kind() == reflect.Ptr {
|
||||
ptr = true
|
||||
v = v.Elem()
|
||||
}
|
||||
return v, ptr
|
||||
}
|
||||
|
||||
// unmarshalSlice unmarshal's the next value, which must be a slice, into
|
||||
// ptrSlice, which must be a pointer to a slice. newValue() can be use to
|
||||
// create the slice.
|
||||
func unmarshalSlice(dec *json.Decoder, ptrSlice reflect.Value) error {
|
||||
if ptrSlice.Kind() != reflect.Ptr || ptrSlice.Elem().Kind() != reflect.Slice {
|
||||
panic("unmarshalSlice called on non-*[]slice value")
|
||||
}
|
||||
sliceValueType := ptrSlice.Elem().Type().Elem()
|
||||
walk := sliceWalk{
|
||||
dec: dec,
|
||||
s: ptrSlice,
|
||||
valueType: sliceValueType,
|
||||
}
|
||||
if err := walk.run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type sliceWalk struct {
|
||||
dec *json.Decoder
|
||||
s reflect.Value // *[]slice
|
||||
valueType reflect.Type
|
||||
}
|
||||
|
||||
// run runs our decoder state machine.
|
||||
func (s *sliceWalk) run() error {
|
||||
var state = s.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sliceWalk) start() (stateFn, error) {
|
||||
// slices can have custom unmarshaler's.
|
||||
if hasUnmarshalJSON(s.s) {
|
||||
err := s.dec.Decode(s.s.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We only want to use this if the slice value is:
|
||||
// []*struct/[]struct/[]map/[]slice
|
||||
// otherwise use standard decode
|
||||
t := s.valueBaseType()
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("cannot unmarshal into a **<type> or *<reference>")
|
||||
case reflect.Struct, reflect.Map, reflect.Slice:
|
||||
delim, err := s.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// This indicates the value was set to nil.
|
||||
if delim == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if !delimIs(delim, '[') {
|
||||
return nil, fmt.Errorf("Unmarshal expected opening [, received %v", delim)
|
||||
}
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
if err := s.dec.Decode(s.s.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *sliceWalk) next() (stateFn, error) {
|
||||
if s.dec.More() {
|
||||
return s.storeValue, nil
|
||||
}
|
||||
// Nothing left in the slice, remove closing ]
|
||||
_, err := s.dec.Token()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *sliceWalk) storeValue() (stateFn, error) {
|
||||
t := s.valueBaseType()
|
||||
switch t.Kind() {
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("do not support 'pointer to pointer' or 'pointer to reference' types")
|
||||
case reflect.Struct:
|
||||
return s.storeStruct, nil
|
||||
case reflect.Map:
|
||||
return s.storeMap, nil
|
||||
case reflect.Slice:
|
||||
return s.storeSlice, nil
|
||||
}
|
||||
return nil, fmt.Errorf("bug: sliceWalk.storeValue() called on unsupported type: %v", t.Kind())
|
||||
}
|
||||
|
||||
func (s *sliceWalk) storeStruct() (stateFn, error) {
|
||||
v := newValue(s.valueType)
|
||||
if err := unmarshalStruct(s.dec, v.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.valueType.Kind() == reflect.Ptr {
|
||||
s.s.Elem().Set(reflect.Append(s.s.Elem(), v))
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
s.s.Elem().Set(reflect.Append(s.s.Elem(), v.Elem()))
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
func (s *sliceWalk) storeMap() (stateFn, error) {
|
||||
v := reflect.MakeMap(s.valueType)
|
||||
ptr := newValue(v.Type())
|
||||
ptr.Elem().Set(v)
|
||||
|
||||
if err := unmarshalMap(s.dec, ptr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.s.Elem().Set(reflect.Append(s.s.Elem(), v))
|
||||
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
func (s *sliceWalk) storeSlice() (stateFn, error) {
|
||||
v := newValue(s.valueType)
|
||||
if err := unmarshalSlice(s.dec, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.s.Elem().Set(reflect.Append(s.s.Elem(), v.Elem()))
|
||||
|
||||
return s.next, nil
|
||||
}
|
||||
|
||||
// valueType returns the underlying Type. So a *struct would yield
|
||||
// struct, etc...
|
||||
func (s *sliceWalk) valueBaseType() reflect.Type {
|
||||
v := s.valueType
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// newValue() returns a new *type that represents type passed.
|
||||
func newValue(valueType reflect.Type) reflect.Value {
|
||||
if valueType.Kind() == reflect.Ptr {
|
||||
return reflect.New(valueType.Elem())
|
||||
}
|
||||
return reflect.New(valueType)
|
||||
}
|
||||
Generated
Vendored
+346
@@ -0,0 +1,346 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// marshalStruct takes in i, which must be a *struct or struct and marshals its content
|
||||
// as JSON into buff (sometimes with writes to buff directly, sometimes via enc).
|
||||
// This call is recursive for all fields of *struct or struct type.
|
||||
func marshalStruct(v reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
// We only care about custom Marshalling a struct.
|
||||
if v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("bug: marshal() received a non *struct or struct, received type %T", v.Interface())
|
||||
}
|
||||
|
||||
if hasMarshalJSON(v) {
|
||||
b, err := callMarshalJSON(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buff.Write(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
|
||||
// If it has an AdditionalFields field make sure its the right type.
|
||||
f := v.FieldByName(addField)
|
||||
if f.Kind() != reflect.Invalid {
|
||||
if f.Kind() != reflect.Map {
|
||||
return fmt.Errorf("type %T has field 'AdditionalFields' that is not a map[string]interface{}", v.Interface())
|
||||
}
|
||||
if !f.Type().AssignableTo(mapStrInterType) {
|
||||
return fmt.Errorf("type %T has field 'AdditionalFields' that is not a map[string]interface{}", v.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
translator, err := findFields(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buff.WriteByte(leftBrace)
|
||||
for x := 0; x < v.NumField(); x++ {
|
||||
field := v.Field(x)
|
||||
|
||||
// We don't access private fields.
|
||||
if unicode.IsLower(rune(t.Field(x).Name[0])) {
|
||||
continue
|
||||
}
|
||||
|
||||
if t.Field(x).Name == addField {
|
||||
if v.Field(x).Len() > 0 {
|
||||
if err := writeAddFields(field.Interface(), buff, enc); err != nil {
|
||||
return err
|
||||
}
|
||||
buff.WriteByte(comma)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If they have omitempty set, we don't write out the field if
|
||||
// it is the zero value.
|
||||
if hasOmitEmpty(t.Field(x).Tag.Get("json")) {
|
||||
if v.Field(x).IsZero() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the field name part.
|
||||
jsonName := translator.jsonName(t.Field(x).Name)
|
||||
buff.WriteString(fmt.Sprintf("%q:", jsonName))
|
||||
|
||||
if field.Kind() == reflect.Ptr {
|
||||
field = field.Elem()
|
||||
}
|
||||
|
||||
if err := marshalStructField(field, buff, enc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
buff.Truncate(buff.Len() - 1) // Remove final comma
|
||||
buff.WriteByte(rightBrace)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalStructField(field reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
// Determine if we need a trailing comma.
|
||||
defer buff.WriteByte(comma)
|
||||
|
||||
switch field.Kind() {
|
||||
// If it was a *struct or struct, we need to recursively all marshal().
|
||||
case reflect.Struct:
|
||||
if field.CanAddr() {
|
||||
field = field.Addr()
|
||||
}
|
||||
return marshalStruct(field, buff, enc)
|
||||
case reflect.Map:
|
||||
return marshalMap(field, buff, enc)
|
||||
case reflect.Slice:
|
||||
return marshalSlice(field, buff, enc)
|
||||
}
|
||||
|
||||
// It is just a basic type, so encode it.
|
||||
if err := enc.Encode(field.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
buff.Truncate(buff.Len() - 1) // Remove Encode() added \n
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalMap(v reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
if v.Kind() != reflect.Map {
|
||||
return fmt.Errorf("bug: marshalMap() called on %T", v.Interface())
|
||||
}
|
||||
if v.Len() == 0 {
|
||||
buff.WriteByte(leftBrace)
|
||||
buff.WriteByte(rightBrace)
|
||||
return nil
|
||||
}
|
||||
encoder := mapEncode{m: v, buff: buff, enc: enc}
|
||||
return encoder.run()
|
||||
}
|
||||
|
||||
type mapEncode struct {
|
||||
m reflect.Value
|
||||
buff *bytes.Buffer
|
||||
enc *json.Encoder
|
||||
|
||||
valueBaseType reflect.Type
|
||||
}
|
||||
|
||||
// run runs our encoder state machine.
|
||||
func (m *mapEncode) run() error {
|
||||
var state = m.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mapEncode) start() (stateFn, error) {
|
||||
if hasMarshalJSON(m.m) {
|
||||
b, err := callMarshalJSON(m.m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.buff.Write(b)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
valueBaseType := m.m.Type().Elem()
|
||||
if valueBaseType.Kind() == reflect.Ptr {
|
||||
valueBaseType = valueBaseType.Elem()
|
||||
}
|
||||
m.valueBaseType = valueBaseType
|
||||
|
||||
switch valueBaseType.Kind() {
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("Marshal does not support **<type> or *<reference>")
|
||||
case reflect.Struct, reflect.Map, reflect.Slice:
|
||||
return m.encode, nil
|
||||
}
|
||||
|
||||
// If the map value doesn't have a struct/map/slice, just Encode() it.
|
||||
if err := m.enc.Encode(m.m.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.buff.Truncate(m.buff.Len() - 1) // Remove Encode() added \n
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mapEncode) encode() (stateFn, error) {
|
||||
m.buff.WriteByte(leftBrace)
|
||||
|
||||
iter := m.m.MapRange()
|
||||
for iter.Next() {
|
||||
// Write the key.
|
||||
k := iter.Key()
|
||||
m.buff.WriteString(fmt.Sprintf("%q:", k.String()))
|
||||
|
||||
v := iter.Value()
|
||||
switch m.valueBaseType.Kind() {
|
||||
case reflect.Struct:
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
if err := marshalStruct(v, m.buff, m.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Map:
|
||||
if err := marshalMap(v, m.buff, m.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Slice:
|
||||
if err := marshalSlice(v, m.buff, m.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("critical bug: mapEncode.encode() called with value base type: %v", m.valueBaseType.Kind()))
|
||||
}
|
||||
m.buff.WriteByte(comma)
|
||||
}
|
||||
m.buff.Truncate(m.buff.Len() - 1) // Remove final comma
|
||||
m.buff.WriteByte(rightBrace)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func marshalSlice(v reflect.Value, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
if v.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("bug: marshalSlice() called on %T", v.Interface())
|
||||
}
|
||||
if v.Len() == 0 {
|
||||
buff.WriteByte(leftParen)
|
||||
buff.WriteByte(rightParen)
|
||||
return nil
|
||||
}
|
||||
encoder := sliceEncode{s: v, buff: buff, enc: enc}
|
||||
return encoder.run()
|
||||
}
|
||||
|
||||
type sliceEncode struct {
|
||||
s reflect.Value
|
||||
buff *bytes.Buffer
|
||||
enc *json.Encoder
|
||||
|
||||
valueBaseType reflect.Type
|
||||
}
|
||||
|
||||
// run runs our encoder state machine.
|
||||
func (s *sliceEncode) run() error {
|
||||
var state = s.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sliceEncode) start() (stateFn, error) {
|
||||
if hasMarshalJSON(s.s) {
|
||||
b, err := callMarshalJSON(s.s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.buff.Write(b)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
valueBaseType := s.s.Type().Elem()
|
||||
if valueBaseType.Kind() == reflect.Ptr {
|
||||
valueBaseType = valueBaseType.Elem()
|
||||
}
|
||||
s.valueBaseType = valueBaseType
|
||||
|
||||
switch valueBaseType.Kind() {
|
||||
case reflect.Ptr:
|
||||
return nil, fmt.Errorf("Marshal does not support **<type> or *<reference>")
|
||||
case reflect.Struct, reflect.Map, reflect.Slice:
|
||||
return s.encode, nil
|
||||
}
|
||||
|
||||
// If the map value doesn't have a struct/map/slice, just Encode() it.
|
||||
if err := s.enc.Encode(s.s.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.buff.Truncate(s.buff.Len() - 1) // Remove Encode added \n
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *sliceEncode) encode() (stateFn, error) {
|
||||
s.buff.WriteByte(leftParen)
|
||||
for i := 0; i < s.s.Len(); i++ {
|
||||
v := s.s.Index(i)
|
||||
switch s.valueBaseType.Kind() {
|
||||
case reflect.Struct:
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
if err := marshalStruct(v, s.buff, s.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Map:
|
||||
if err := marshalMap(v, s.buff, s.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Slice:
|
||||
if err := marshalSlice(v, s.buff, s.enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("critical bug: mapEncode.encode() called with value base type: %v", s.valueBaseType.Kind()))
|
||||
}
|
||||
s.buff.WriteByte(comma)
|
||||
}
|
||||
s.buff.Truncate(s.buff.Len() - 1) // Remove final comma
|
||||
s.buff.WriteByte(rightParen)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// writeAddFields writes the AdditionalFields struct field out to JSON as field
|
||||
// values. i must be a map[string]interface{} or this will panic.
|
||||
func writeAddFields(i interface{}, buff *bytes.Buffer, enc *json.Encoder) error {
|
||||
m := i.(map[string]interface{})
|
||||
|
||||
x := 0
|
||||
for k, v := range m {
|
||||
buff.WriteString(fmt.Sprintf("%q:", k))
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return err
|
||||
}
|
||||
buff.Truncate(buff.Len() - 1) // Remove Encode() added \n
|
||||
|
||||
if x+1 != len(m) {
|
||||
buff.WriteByte(comma)
|
||||
}
|
||||
x++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Generated
Vendored
+290
@@ -0,0 +1,290 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func unmarshalStruct(jdec *json.Decoder, i interface{}) error {
|
||||
v := reflect.ValueOf(i)
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("Unmarshal() received type %T, which is not a *struct", i)
|
||||
}
|
||||
v = v.Elem()
|
||||
if v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("Unmarshal() received type %T, which is not a *struct", i)
|
||||
}
|
||||
|
||||
if hasUnmarshalJSON(v) {
|
||||
// Indicates that this type has a custom Unmarshaler.
|
||||
return jdec.Decode(v.Addr().Interface())
|
||||
}
|
||||
|
||||
f := v.FieldByName(addField)
|
||||
if f.Kind() == reflect.Invalid {
|
||||
return fmt.Errorf("Unmarshal(%T) only supports structs that have the field AdditionalFields or implements json.Unmarshaler", i)
|
||||
}
|
||||
|
||||
if f.Kind() != reflect.Map || !f.Type().AssignableTo(mapStrInterType) {
|
||||
return fmt.Errorf("type %T has field 'AdditionalFields' that is not a map[string]interface{}", i)
|
||||
}
|
||||
|
||||
dec := newDecoder(jdec, v)
|
||||
return dec.run()
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
dec *json.Decoder
|
||||
value reflect.Value // This will be a reflect.Struct
|
||||
translator translateFields
|
||||
key string
|
||||
}
|
||||
|
||||
func newDecoder(dec *json.Decoder, value reflect.Value) *decoder {
|
||||
return &decoder{value: value, dec: dec}
|
||||
}
|
||||
|
||||
// run runs our decoder state machine.
|
||||
func (d *decoder) run() error {
|
||||
var state = d.start
|
||||
var err error
|
||||
for {
|
||||
state, err = state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// start looks for our opening delimeter '{' and then transitions to looping through our fields.
|
||||
func (d *decoder) start() (stateFn, error) {
|
||||
var err error
|
||||
d.translator, err = findFields(d.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delim, err := d.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !delimIs(delim, '{') {
|
||||
return nil, fmt.Errorf("Unmarshal expected opening {, received %v", delim)
|
||||
}
|
||||
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
// next gets the next struct field name from the raw json or stops the machine if we get our closing }.
|
||||
func (d *decoder) next() (stateFn, error) {
|
||||
if !d.dec.More() {
|
||||
// Remove the closing }.
|
||||
if _, err := d.dec.Token(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
key, err := d.dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.key = key.(string)
|
||||
return d.storeValue, nil
|
||||
}
|
||||
|
||||
// storeValue takes the next value and stores it our struct. If the field can't be found
|
||||
// in the struct, it pushes the operation to storeAdditional().
|
||||
func (d *decoder) storeValue() (stateFn, error) {
|
||||
goName := d.translator.goName(d.key)
|
||||
if goName == "" {
|
||||
goName = d.key
|
||||
}
|
||||
|
||||
// We don't have the field in the struct, so it goes in AdditionalFields.
|
||||
f := d.value.FieldByName(goName)
|
||||
if f.Kind() == reflect.Invalid {
|
||||
return d.storeAdditional, nil
|
||||
}
|
||||
|
||||
// Indicates that this type has a custom Unmarshaler.
|
||||
if hasUnmarshalJSON(f) {
|
||||
err := d.dec.Decode(f.Addr().Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
t, isPtr, err := fieldBaseType(d.value, goName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("type(%s) had field(%s) %w", d.value.Type().Name(), goName, err)
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
// We need to recursively call ourselves on any *struct or struct.
|
||||
case reflect.Struct:
|
||||
if isPtr {
|
||||
if f.IsNil() {
|
||||
f.Set(reflect.New(t))
|
||||
}
|
||||
} else {
|
||||
f = f.Addr()
|
||||
}
|
||||
if err := unmarshalStruct(d.dec, f.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.next, nil
|
||||
case reflect.Map:
|
||||
v := reflect.MakeMap(f.Type())
|
||||
ptr := newValue(f.Type())
|
||||
ptr.Elem().Set(v)
|
||||
if err := unmarshalMap(d.dec, ptr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Set(ptr.Elem())
|
||||
return d.next, nil
|
||||
case reflect.Slice:
|
||||
v := reflect.MakeSlice(f.Type(), 0, 0)
|
||||
ptr := newValue(f.Type())
|
||||
ptr.Elem().Set(v)
|
||||
if err := unmarshalSlice(d.dec, ptr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Set(ptr.Elem())
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
if !isPtr {
|
||||
f = f.Addr()
|
||||
}
|
||||
|
||||
// For values that are pointers, we need them to be non-nil in order
|
||||
// to decode into them.
|
||||
if f.IsNil() {
|
||||
f.Set(reflect.New(t))
|
||||
}
|
||||
|
||||
if err := d.dec.Decode(f.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
// storeAdditional pushes the key/value into our .AdditionalFields map.
|
||||
func (d *decoder) storeAdditional() (stateFn, error) {
|
||||
rw := json.RawMessage{}
|
||||
if err := d.dec.Decode(&rw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
field := d.value.FieldByName(addField)
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.MakeMap(field.Type()))
|
||||
}
|
||||
field.SetMapIndex(reflect.ValueOf(d.key), reflect.ValueOf(rw))
|
||||
return d.next, nil
|
||||
}
|
||||
|
||||
func fieldBaseType(v reflect.Value, fieldName string) (t reflect.Type, isPtr bool, err error) {
|
||||
sf, ok := v.Type().FieldByName(fieldName)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("bug: fieldBaseType() lookup of field(%s) on type(%s): do not have field", fieldName, v.Type().Name())
|
||||
}
|
||||
t = sf.Type
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
isPtr = true
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
return nil, isPtr, fmt.Errorf("received pointer to pointer type, not supported")
|
||||
}
|
||||
return t, isPtr, nil
|
||||
}
|
||||
|
||||
type translateField struct {
|
||||
jsonName string
|
||||
goName string
|
||||
}
|
||||
|
||||
// translateFields is a list of translateFields with a handy lookup method.
|
||||
type translateFields []translateField
|
||||
|
||||
// goName loops through a list of fields looking for one contaning the jsonName and
|
||||
// returning the goName. If not found, returns the empty string.
|
||||
// Note: not a map because at this size slices are faster even in tight loops.
|
||||
func (t translateFields) goName(jsonName string) string {
|
||||
for _, entry := range t {
|
||||
if entry.jsonName == jsonName {
|
||||
return entry.goName
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// jsonName loops through a list of fields looking for one contaning the goName and
|
||||
// returning the jsonName. If not found, returns the empty string.
|
||||
// Note: not a map because at this size slices are faster even in tight loops.
|
||||
func (t translateFields) jsonName(goName string) string {
|
||||
for _, entry := range t {
|
||||
if entry.goName == goName {
|
||||
return entry.jsonName
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var umarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
|
||||
|
||||
// findFields parses a struct and writes the field tags for lookup. It will return an error
|
||||
// if any field has a type of *struct or struct that does not implement json.Marshaler.
|
||||
func findFields(v reflect.Value) (translateFields, error) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("findFields received a %s type, expected *struct or struct", v.Type().Name())
|
||||
}
|
||||
tfs := make([]translateField, 0, v.NumField())
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
tf := translateField{
|
||||
goName: v.Type().Field(i).Name,
|
||||
jsonName: parseTag(v.Type().Field(i).Tag.Get("json")),
|
||||
}
|
||||
switch tf.jsonName {
|
||||
case "", "-":
|
||||
tf.jsonName = tf.goName
|
||||
}
|
||||
tfs = append(tfs, tf)
|
||||
|
||||
f := v.Field(i)
|
||||
if f.Kind() == reflect.Ptr {
|
||||
f = f.Elem()
|
||||
}
|
||||
if f.Kind() == reflect.Struct {
|
||||
if f.Type().Implements(umarshalerType) {
|
||||
return nil, fmt.Errorf("struct type %q which has field %q which "+
|
||||
"doesn't implement json.Unmarshaler", v.Type().Name(), v.Type().Field(i).Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tfs, nil
|
||||
}
|
||||
|
||||
// parseTag just returns the first entry in the tag. tag is the string
|
||||
// returned by reflect.StructField.Tag().Get().
|
||||
func parseTag(tag string) string {
|
||||
if idx := strings.Index(tag, ","); idx != -1 {
|
||||
return tag[:idx]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
Generated
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package time provides for custom types to translate time from JSON and other formats
|
||||
// into time.Time objects.
|
||||
package time
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Unix provides a type that can marshal and unmarshal a string representation
|
||||
// of the unix epoch into a time.Time object.
|
||||
type Unix struct {
|
||||
T time.Time
|
||||
}
|
||||
|
||||
// MarshalJSON implements encoding/json.MarshalJSON().
|
||||
func (u Unix) MarshalJSON() ([]byte, error) {
|
||||
if u.T.IsZero() {
|
||||
return []byte(""), nil
|
||||
}
|
||||
return []byte(fmt.Sprintf("%q", strconv.FormatInt(u.T.Unix(), 10))), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements encoding/json.UnmarshalJSON().
|
||||
func (u *Unix) UnmarshalJSON(b []byte) error {
|
||||
i, err := strconv.Atoi(strings.Trim(string(b), `"`))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unix time(%s) could not be converted from string to int: %w", string(b), err)
|
||||
}
|
||||
u.T = time.Unix(int64(i), 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DurationTime provides a type that can marshal and unmarshal a string representation
|
||||
// of a duration from now into a time.Time object.
|
||||
// Note: I'm not sure this is the best way to do this. What happens is we get a field
|
||||
// called "expires_in" that represents the seconds from now that this expires. We
|
||||
// turn that into a time we call .ExpiresOn. But maybe we should be recording
|
||||
// when the token was received at .TokenRecieved and .ExpiresIn should remain as a duration.
|
||||
// Then we could have a method called ExpiresOn(). Honestly, the whole thing is
|
||||
// bad because the server doesn't return a concrete time. I think this is
|
||||
// cleaner, but its not great either.
|
||||
type DurationTime struct {
|
||||
T time.Time
|
||||
}
|
||||
|
||||
// MarshalJSON implements encoding/json.MarshalJSON().
|
||||
func (d DurationTime) MarshalJSON() ([]byte, error) {
|
||||
if d.T.IsZero() {
|
||||
return []byte(""), nil
|
||||
}
|
||||
|
||||
dt := time.Until(d.T)
|
||||
return []byte(fmt.Sprintf("%d", int64(dt*time.Second))), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements encoding/json.UnmarshalJSON().
|
||||
func (d *DurationTime) UnmarshalJSON(b []byte) error {
|
||||
i, err := strconv.Atoi(strings.Trim(string(b), `"`))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unix time(%s) could not be converted from string to int: %w", string(b), err)
|
||||
}
|
||||
d.T = time.Now().Add(time.Duration(i) * time.Second)
|
||||
return nil
|
||||
}
|
||||
Generated
Vendored
+355
@@ -0,0 +1,355 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported"
|
||||
internalTime "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs"
|
||||
)
|
||||
|
||||
// ResolveEndpointer contains the methods for resolving authority endpoints.
|
||||
type ResolveEndpointer interface {
|
||||
ResolveEndpoints(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, error)
|
||||
}
|
||||
|
||||
// AccessTokens contains the methods for fetching tokens from different sources.
|
||||
type AccessTokens interface {
|
||||
DeviceCodeResult(ctx context.Context, authParameters authority.AuthParams) (accesstokens.DeviceCodeResult, error)
|
||||
FromUsernamePassword(ctx context.Context, authParameters authority.AuthParams) (accesstokens.TokenResponse, error)
|
||||
FromAuthCode(ctx context.Context, req accesstokens.AuthCodeRequest) (accesstokens.TokenResponse, error)
|
||||
FromRefreshToken(ctx context.Context, appType accesstokens.AppType, authParams authority.AuthParams, cc *accesstokens.Credential, refreshToken string) (accesstokens.TokenResponse, error)
|
||||
FromClientSecret(ctx context.Context, authParameters authority.AuthParams, clientSecret string) (accesstokens.TokenResponse, error)
|
||||
FromAssertion(ctx context.Context, authParameters authority.AuthParams, assertion string) (accesstokens.TokenResponse, error)
|
||||
FromUserAssertionClientSecret(ctx context.Context, authParameters authority.AuthParams, userAssertion string, clientSecret string) (accesstokens.TokenResponse, error)
|
||||
FromUserAssertionClientCertificate(ctx context.Context, authParameters authority.AuthParams, userAssertion string, assertion string) (accesstokens.TokenResponse, error)
|
||||
FromDeviceCodeResult(ctx context.Context, authParameters authority.AuthParams, deviceCodeResult accesstokens.DeviceCodeResult) (accesstokens.TokenResponse, error)
|
||||
FromSamlGrant(ctx context.Context, authParameters authority.AuthParams, samlGrant wstrust.SamlTokenInfo) (accesstokens.TokenResponse, error)
|
||||
}
|
||||
|
||||
// FetchAuthority will be implemented by authority.Authority.
|
||||
type FetchAuthority interface {
|
||||
UserRealm(context.Context, authority.AuthParams) (authority.UserRealm, error)
|
||||
AADInstanceDiscovery(context.Context, authority.Info) (authority.InstanceDiscoveryResponse, error)
|
||||
}
|
||||
|
||||
// FetchWSTrust contains the methods for interacting with WSTrust endpoints.
|
||||
type FetchWSTrust interface {
|
||||
Mex(ctx context.Context, federationMetadataURL string) (defs.MexDocument, error)
|
||||
SAMLTokenInfo(ctx context.Context, authParameters authority.AuthParams, cloudAudienceURN string, endpoint defs.Endpoint) (wstrust.SamlTokenInfo, error)
|
||||
}
|
||||
|
||||
// Client provides tokens for various types of token requests.
|
||||
type Client struct {
|
||||
Resolver ResolveEndpointer
|
||||
AccessTokens AccessTokens
|
||||
Authority FetchAuthority
|
||||
WSTrust FetchWSTrust
|
||||
}
|
||||
|
||||
// New is the constructor for Token.
|
||||
func New(httpClient ops.HTTPClient) *Client {
|
||||
r := ops.New(httpClient)
|
||||
return &Client{
|
||||
Resolver: newAuthorityEndpoint(r),
|
||||
AccessTokens: r.AccessTokens(),
|
||||
Authority: r.Authority(),
|
||||
WSTrust: r.WSTrust(),
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveEndpoints gets the authorization and token endpoints and creates an AuthorityEndpoints instance.
|
||||
func (t *Client) ResolveEndpoints(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, error) {
|
||||
return t.Resolver.ResolveEndpoints(ctx, authorityInfo, userPrincipalName)
|
||||
}
|
||||
|
||||
// AADInstanceDiscovery attempts to discover a tenant endpoint (used in OIDC auth with an authorization endpoint).
|
||||
// This is done by AAD which allows for aliasing of tenants (windows.sts.net is the same as login.windows.com).
|
||||
func (t *Client) AADInstanceDiscovery(ctx context.Context, authorityInfo authority.Info) (authority.InstanceDiscoveryResponse, error) {
|
||||
return t.Authority.AADInstanceDiscovery(ctx, authorityInfo)
|
||||
}
|
||||
|
||||
// AuthCode returns a token based on an authorization code.
|
||||
func (t *Client) AuthCode(ctx context.Context, req accesstokens.AuthCodeRequest) (accesstokens.TokenResponse, error) {
|
||||
if err := scopeError(req.AuthParams); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
if err := t.resolveEndpoint(ctx, &req.AuthParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
tResp, err := t.AccessTokens.FromAuthCode(ctx, req)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, fmt.Errorf("could not retrieve token from auth code: %w", err)
|
||||
}
|
||||
return tResp, nil
|
||||
}
|
||||
|
||||
// Credential acquires a token from the authority using a client credentials grant.
|
||||
func (t *Client) Credential(ctx context.Context, authParams authority.AuthParams, cred *accesstokens.Credential) (accesstokens.TokenResponse, error) {
|
||||
if cred.TokenProvider != nil {
|
||||
now := time.Now()
|
||||
scopes := make([]string, len(authParams.Scopes))
|
||||
copy(scopes, authParams.Scopes)
|
||||
params := exported.TokenProviderParameters{
|
||||
Claims: authParams.Claims,
|
||||
CorrelationID: uuid.New().String(),
|
||||
Scopes: scopes,
|
||||
TenantID: authParams.AuthorityInfo.Tenant,
|
||||
}
|
||||
tr, err := cred.TokenProvider(ctx, params)
|
||||
if err != nil {
|
||||
if len(scopes) == 0 {
|
||||
err = fmt.Errorf("token request had an empty authority.AuthParams.Scopes, which may cause the following error: %w", err)
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return accesstokens.TokenResponse{
|
||||
TokenType: authParams.AuthnScheme.AccessTokenType(),
|
||||
AccessToken: tr.AccessToken,
|
||||
ExpiresOn: internalTime.DurationTime{
|
||||
T: now.Add(time.Duration(tr.ExpiresInSeconds) * time.Second),
|
||||
},
|
||||
GrantedScopes: accesstokens.Scopes{Slice: authParams.Scopes},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
if cred.Secret != "" {
|
||||
return t.AccessTokens.FromClientSecret(ctx, authParams, cred.Secret)
|
||||
}
|
||||
jwt, err := cred.JWT(ctx, authParams)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return t.AccessTokens.FromAssertion(ctx, authParams, jwt)
|
||||
}
|
||||
|
||||
// Credential acquires a token from the authority using a client credentials grant.
|
||||
func (t *Client) OnBehalfOf(ctx context.Context, authParams authority.AuthParams, cred *accesstokens.Credential) (accesstokens.TokenResponse, error) {
|
||||
if err := scopeError(authParams); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
if cred.Secret != "" {
|
||||
return t.AccessTokens.FromUserAssertionClientSecret(ctx, authParams, authParams.UserAssertion, cred.Secret)
|
||||
}
|
||||
jwt, err := cred.JWT(ctx, authParams)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
tr, err := t.AccessTokens.FromUserAssertionClientCertificate(ctx, authParams, authParams.UserAssertion, jwt)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
func (t *Client) Refresh(ctx context.Context, reqType accesstokens.AppType, authParams authority.AuthParams, cc *accesstokens.Credential, refreshToken accesstokens.RefreshToken) (accesstokens.TokenResponse, error) {
|
||||
if err := scopeError(authParams); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
tr, err := t.AccessTokens.FromRefreshToken(ctx, reqType, authParams, cc, refreshToken.Secret)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// UsernamePassword retrieves a token where a username and password is used. However, if this is
|
||||
// a user realm of "Federated", this uses SAML tokens. If "Managed", uses normal username/password.
|
||||
func (t *Client) UsernamePassword(ctx context.Context, authParams authority.AuthParams) (accesstokens.TokenResponse, error) {
|
||||
if err := scopeError(authParams); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
if authParams.AuthorityInfo.AuthorityType == authority.ADFS {
|
||||
if err := t.resolveEndpoint(ctx, &authParams, authParams.Username); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return t.AccessTokens.FromUsernamePassword(ctx, authParams)
|
||||
}
|
||||
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
userRealm, err := t.Authority.UserRealm(ctx, authParams)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, fmt.Errorf("problem getting user realm from authority: %w", err)
|
||||
}
|
||||
|
||||
switch userRealm.AccountType {
|
||||
case authority.Federated:
|
||||
mexDoc, err := t.WSTrust.Mex(ctx, userRealm.FederationMetadataURL)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("problem getting mex doc from federated url(%s): %w", userRealm.FederationMetadataURL, err)
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
|
||||
saml, err := t.WSTrust.SAMLTokenInfo(ctx, authParams, userRealm.CloudAudienceURN, mexDoc.UsernamePasswordEndpoint)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("problem getting SAML token info: %w", err)
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
tr, err := t.AccessTokens.FromSamlGrant(ctx, authParams, saml)
|
||||
if err != nil {
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return tr, nil
|
||||
case authority.Managed:
|
||||
if len(authParams.Scopes) == 0 {
|
||||
err = fmt.Errorf("token request had an empty authority.AuthParams.Scopes, which may cause the following error: %w", err)
|
||||
return accesstokens.TokenResponse{}, err
|
||||
}
|
||||
return t.AccessTokens.FromUsernamePassword(ctx, authParams)
|
||||
}
|
||||
return accesstokens.TokenResponse{}, errors.New("unknown account type")
|
||||
}
|
||||
|
||||
// DeviceCode is the result of a call to Token.DeviceCode().
|
||||
type DeviceCode struct {
|
||||
// Result is the device code result from the first call in the device code flow. This allows
|
||||
// the caller to retrieve the displayed code that is used to authorize on the second device.
|
||||
Result accesstokens.DeviceCodeResult
|
||||
authParams authority.AuthParams
|
||||
|
||||
accessTokens AccessTokens
|
||||
}
|
||||
|
||||
// Token returns a token AFTER the user uses the user code on the second device. This will block
|
||||
// until either: (1) the code is input by the user and the service releases a token, (2) the token
|
||||
// expires, (3) the Context passed to .DeviceCode() is cancelled or expires, (4) some other service
|
||||
// error occurs.
|
||||
func (d DeviceCode) Token(ctx context.Context) (accesstokens.TokenResponse, error) {
|
||||
if d.accessTokens == nil {
|
||||
return accesstokens.TokenResponse{}, fmt.Errorf("DeviceCode was either created outside its package or the creating method had an error. DeviceCode is not valid")
|
||||
}
|
||||
|
||||
var cancel context.CancelFunc
|
||||
if deadline, ok := ctx.Deadline(); !ok || d.Result.ExpiresOn.Before(deadline) {
|
||||
ctx, cancel = context.WithDeadline(ctx, d.Result.ExpiresOn)
|
||||
} else {
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
var interval = 50 * time.Millisecond
|
||||
timer := time.NewTimer(interval)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
timer.Reset(interval)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return accesstokens.TokenResponse{}, ctx.Err()
|
||||
case <-timer.C:
|
||||
interval += interval * 2
|
||||
if interval > 5*time.Second {
|
||||
interval = 5 * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
token, err := d.accessTokens.FromDeviceCodeResult(ctx, d.authParams, d.Result)
|
||||
if err != nil && isWaitDeviceCodeErr(err) {
|
||||
continue
|
||||
}
|
||||
return token, err // This handles if it was a non-wait error or success
|
||||
}
|
||||
}
|
||||
|
||||
type deviceCodeError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func isWaitDeviceCodeErr(err error) bool {
|
||||
var c errors.CallErr
|
||||
if !errors.As(err, &c) {
|
||||
return false
|
||||
}
|
||||
if c.Resp.StatusCode != 400 {
|
||||
return false
|
||||
}
|
||||
var dCErr deviceCodeError
|
||||
defer c.Resp.Body.Close()
|
||||
body, err := io.ReadAll(c.Resp.Body)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
err = json.Unmarshal(body, &dCErr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if dCErr.Error == "authorization_pending" || dCErr.Error == "slow_down" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DeviceCode returns a DeviceCode object that can be used to get the code that must be entered on the second
|
||||
// device and optionally the token once the code has been entered on the second device.
|
||||
func (t *Client) DeviceCode(ctx context.Context, authParams authority.AuthParams) (DeviceCode, error) {
|
||||
if err := scopeError(authParams); err != nil {
|
||||
return DeviceCode{}, err
|
||||
}
|
||||
|
||||
if err := t.resolveEndpoint(ctx, &authParams, ""); err != nil {
|
||||
return DeviceCode{}, err
|
||||
}
|
||||
|
||||
dcr, err := t.AccessTokens.DeviceCodeResult(ctx, authParams)
|
||||
if err != nil {
|
||||
return DeviceCode{}, err
|
||||
}
|
||||
|
||||
return DeviceCode{Result: dcr, authParams: authParams, accessTokens: t.AccessTokens}, nil
|
||||
}
|
||||
|
||||
func (t *Client) resolveEndpoint(ctx context.Context, authParams *authority.AuthParams, userPrincipalName string) error {
|
||||
endpoints, err := t.Resolver.ResolveEndpoints(ctx, authParams.AuthorityInfo, userPrincipalName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to resolve an endpoint: %w", err)
|
||||
}
|
||||
authParams.Endpoints = endpoints
|
||||
return nil
|
||||
}
|
||||
|
||||
// scopeError takes an authority.AuthParams and returns an error
|
||||
// if len(AuthParams.Scope) == 0.
|
||||
func scopeError(a authority.AuthParams) error {
|
||||
// TODO(someone): we could look deeper at the message to determine if
|
||||
// it's a scope error, but this is a good start.
|
||||
/*
|
||||
{error":"invalid_scope","error_description":"AADSTS1002012: The provided value for scope
|
||||
openid offline_access profile is not valid. Client credential flows must have a scope value
|
||||
with /.default suffixed to the resource identifier (application ID URI)...}
|
||||
*/
|
||||
if len(a.Scopes) == 0 {
|
||||
return fmt.Errorf("token request had an empty authority.AuthParams.Scopes, which is invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Generated
Vendored
+457
@@ -0,0 +1,457 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package accesstokens exposes a REST client for querying backend systems to get various types of
|
||||
access tokens (oauth) for use in authentication.
|
||||
|
||||
These calls are of type "application/x-www-form-urlencoded". This means we use url.Values to
|
||||
represent arguments and then encode them into the POST body message. We receive JSON in
|
||||
return for the requests. The request definition is defined in https://tools.ietf.org/html/rfc7521#section-4.2 .
|
||||
*/
|
||||
package accesstokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
|
||||
/* #nosec */
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
grantType = "grant_type"
|
||||
deviceCode = "device_code"
|
||||
clientID = "client_id"
|
||||
clientInfo = "client_info"
|
||||
clientInfoVal = "1"
|
||||
username = "username"
|
||||
password = "password"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=AppType
|
||||
|
||||
// AppType is whether the authorization code flow is for a public or confidential client.
|
||||
type AppType int8
|
||||
|
||||
const (
|
||||
// ATUnknown is the zero value when the type hasn't been set.
|
||||
ATUnknown AppType = iota
|
||||
// ATPublic indicates this if for the Public.Client.
|
||||
ATPublic
|
||||
// ATConfidential indicates this if for the Confidential.Client.
|
||||
ATConfidential
|
||||
)
|
||||
|
||||
type urlFormCaller interface {
|
||||
URLFormCall(ctx context.Context, endpoint string, qv url.Values, resp interface{}) error
|
||||
}
|
||||
|
||||
// DeviceCodeResponse represents the HTTP response received from the device code endpoint
|
||||
type DeviceCodeResponse struct {
|
||||
authority.OAuthResponseBase
|
||||
|
||||
UserCode string `json:"user_code"`
|
||||
DeviceCode string `json:"device_code"`
|
||||
VerificationURL string `json:"verification_url"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Interval int `json:"interval"`
|
||||
Message string `json:"message"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// Convert converts the DeviceCodeResponse to a DeviceCodeResult
|
||||
func (dcr DeviceCodeResponse) Convert(clientID string, scopes []string) DeviceCodeResult {
|
||||
expiresOn := time.Now().UTC().Add(time.Duration(dcr.ExpiresIn) * time.Second)
|
||||
return NewDeviceCodeResult(dcr.UserCode, dcr.DeviceCode, dcr.VerificationURL, expiresOn, dcr.Interval, dcr.Message, clientID, scopes)
|
||||
}
|
||||
|
||||
// Credential represents the credential used in confidential client flows. This can be either
|
||||
// a Secret or Cert/Key.
|
||||
type Credential struct {
|
||||
// Secret contains the credential secret if we are doing auth by secret.
|
||||
Secret string
|
||||
|
||||
// Cert is the public certificate, if we're authenticating by certificate.
|
||||
Cert *x509.Certificate
|
||||
// Key is the private key for signing, if we're authenticating by certificate.
|
||||
Key crypto.PrivateKey
|
||||
// X5c is the JWT assertion's x5c header value, required for SN/I authentication.
|
||||
X5c []string
|
||||
|
||||
// AssertionCallback is a function provided by the application, if we're authenticating by assertion.
|
||||
AssertionCallback func(context.Context, exported.AssertionRequestOptions) (string, error)
|
||||
|
||||
// TokenProvider is a function provided by the application that implements custom authentication
|
||||
// logic for a confidential client
|
||||
TokenProvider func(context.Context, exported.TokenProviderParameters) (exported.TokenProviderResult, error)
|
||||
}
|
||||
|
||||
// JWT gets the jwt assertion when the credential is not using a secret.
|
||||
func (c *Credential) JWT(ctx context.Context, authParams authority.AuthParams) (string, error) {
|
||||
if c.AssertionCallback != nil {
|
||||
options := exported.AssertionRequestOptions{
|
||||
ClientID: authParams.ClientID,
|
||||
TokenEndpoint: authParams.Endpoints.TokenEndpoint,
|
||||
}
|
||||
return c.AssertionCallback(ctx, options)
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
|
||||
"aud": authParams.Endpoints.TokenEndpoint,
|
||||
"exp": json.Number(strconv.FormatInt(time.Now().Add(10*time.Minute).Unix(), 10)),
|
||||
"iss": authParams.ClientID,
|
||||
"jti": uuid.New().String(),
|
||||
"nbf": json.Number(strconv.FormatInt(time.Now().Unix(), 10)),
|
||||
"sub": authParams.ClientID,
|
||||
})
|
||||
token.Header = map[string]interface{}{
|
||||
"alg": "RS256",
|
||||
"typ": "JWT",
|
||||
"x5t": base64.StdEncoding.EncodeToString(thumbprint(c.Cert)),
|
||||
}
|
||||
|
||||
if authParams.SendX5C {
|
||||
token.Header["x5c"] = c.X5c
|
||||
}
|
||||
|
||||
assertion, err := token.SignedString(c.Key)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to sign a JWT token using private key: %w", err)
|
||||
}
|
||||
return assertion, nil
|
||||
}
|
||||
|
||||
// thumbprint runs the asn1.Der bytes through sha1 for use in the x5t parameter of JWT.
|
||||
// https://tools.ietf.org/html/rfc7517#section-4.8
|
||||
func thumbprint(cert *x509.Certificate) []byte {
|
||||
/* #nosec */
|
||||
a := sha1.Sum(cert.Raw)
|
||||
return a[:]
|
||||
}
|
||||
|
||||
// Client represents the REST calls to get tokens from token generator backends.
|
||||
type Client struct {
|
||||
// Comm provides the HTTP transport client.
|
||||
Comm urlFormCaller
|
||||
|
||||
testing bool
|
||||
}
|
||||
|
||||
// FromUsernamePassword uses a username and password to get an access token.
|
||||
func (c Client) FromUsernamePassword(ctx context.Context, authParameters authority.AuthParams) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.Password)
|
||||
qv.Set(username, authParameters.Username)
|
||||
qv.Set(password, authParameters.Password)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
// AuthCodeRequest stores the values required to request a token from the authority using an authorization code
|
||||
type AuthCodeRequest struct {
|
||||
AuthParams authority.AuthParams
|
||||
Code string
|
||||
CodeChallenge string
|
||||
Credential *Credential
|
||||
AppType AppType
|
||||
}
|
||||
|
||||
// NewCodeChallengeRequest returns an AuthCodeRequest that uses a code challenge..
|
||||
func NewCodeChallengeRequest(params authority.AuthParams, appType AppType, cc *Credential, code, challenge string) (AuthCodeRequest, error) {
|
||||
if appType == ATUnknown {
|
||||
return AuthCodeRequest{}, fmt.Errorf("bug: NewCodeChallengeRequest() called with AppType == ATUnknown")
|
||||
}
|
||||
return AuthCodeRequest{
|
||||
AuthParams: params,
|
||||
AppType: appType,
|
||||
Code: code,
|
||||
CodeChallenge: challenge,
|
||||
Credential: cc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FromAuthCode uses an authorization code to retrieve an access token.
|
||||
func (c Client) FromAuthCode(ctx context.Context, req AuthCodeRequest) (TokenResponse, error) {
|
||||
var qv url.Values
|
||||
|
||||
switch req.AppType {
|
||||
case ATUnknown:
|
||||
return TokenResponse{}, fmt.Errorf("bug: Token.AuthCode() received request with AppType == ATUnknown")
|
||||
case ATConfidential:
|
||||
var err error
|
||||
if req.Credential == nil {
|
||||
return TokenResponse{}, fmt.Errorf("AuthCodeRequest had nil Credential for Confidential app")
|
||||
}
|
||||
qv, err = prepURLVals(ctx, req.Credential, req.AuthParams)
|
||||
if err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
case ATPublic:
|
||||
qv = url.Values{}
|
||||
default:
|
||||
return TokenResponse{}, fmt.Errorf("bug: Token.AuthCode() received request with AppType == %v, which we do not recongnize", req.AppType)
|
||||
}
|
||||
|
||||
qv.Set(grantType, grant.AuthCode)
|
||||
qv.Set("code", req.Code)
|
||||
qv.Set("code_verifier", req.CodeChallenge)
|
||||
qv.Set("redirect_uri", req.AuthParams.Redirecturi)
|
||||
qv.Set(clientID, req.AuthParams.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
addScopeQueryParam(qv, req.AuthParams)
|
||||
if err := addClaims(qv, req.AuthParams); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
|
||||
return c.doTokenResp(ctx, req.AuthParams, qv)
|
||||
}
|
||||
|
||||
// FromRefreshToken uses a refresh token (for refreshing credentials) to get a new access token.
|
||||
func (c Client) FromRefreshToken(ctx context.Context, appType AppType, authParams authority.AuthParams, cc *Credential, refreshToken string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if appType == ATConfidential {
|
||||
var err error
|
||||
qv, err = prepURLVals(ctx, cc, authParams)
|
||||
if err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
}
|
||||
if err := addClaims(qv, authParams); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.RefreshToken)
|
||||
qv.Set(clientID, authParams.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
qv.Set("refresh_token", refreshToken)
|
||||
addScopeQueryParam(qv, authParams)
|
||||
|
||||
return c.doTokenResp(ctx, authParams, qv)
|
||||
}
|
||||
|
||||
// FromClientSecret uses a client's secret (aka password) to get a new token.
|
||||
func (c Client) FromClientSecret(ctx context.Context, authParameters authority.AuthParams, clientSecret string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.ClientCredential)
|
||||
qv.Set("client_secret", clientSecret)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
token, err := c.doTokenResp(ctx, authParameters, qv)
|
||||
if err != nil {
|
||||
return token, fmt.Errorf("FromClientSecret(): %w", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (c Client) FromAssertion(ctx context.Context, authParameters authority.AuthParams, assertion string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.ClientCredential)
|
||||
qv.Set("client_assertion_type", grant.ClientAssertion)
|
||||
qv.Set("client_assertion", assertion)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
token, err := c.doTokenResp(ctx, authParameters, qv)
|
||||
if err != nil {
|
||||
return token, fmt.Errorf("FromAssertion(): %w", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (c Client) FromUserAssertionClientSecret(ctx context.Context, authParameters authority.AuthParams, userAssertion string, clientSecret string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.JWT)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set("client_secret", clientSecret)
|
||||
qv.Set("assertion", userAssertion)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
qv.Set("requested_token_use", "on_behalf_of")
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
func (c Client) FromUserAssertionClientCertificate(ctx context.Context, authParameters authority.AuthParams, userAssertion string, assertion string) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.JWT)
|
||||
qv.Set("client_assertion_type", grant.ClientAssertion)
|
||||
qv.Set("client_assertion", assertion)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set("assertion", userAssertion)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
qv.Set("requested_token_use", "on_behalf_of")
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
func (c Client) DeviceCodeResult(ctx context.Context, authParameters authority.AuthParams) (DeviceCodeResult, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return DeviceCodeResult{}, err
|
||||
}
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
endpoint := strings.Replace(authParameters.Endpoints.TokenEndpoint, "token", "devicecode", -1)
|
||||
|
||||
resp := DeviceCodeResponse{}
|
||||
err := c.Comm.URLFormCall(ctx, endpoint, qv, &resp)
|
||||
if err != nil {
|
||||
return DeviceCodeResult{}, err
|
||||
}
|
||||
|
||||
return resp.Convert(authParameters.ClientID, authParameters.Scopes), nil
|
||||
}
|
||||
|
||||
func (c Client) FromDeviceCodeResult(ctx context.Context, authParameters authority.AuthParams, deviceCodeResult DeviceCodeResult) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(grantType, grant.DeviceCode)
|
||||
qv.Set(deviceCode, deviceCodeResult.DeviceCode)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
func (c Client) FromSamlGrant(ctx context.Context, authParameters authority.AuthParams, samlGrant wstrust.SamlTokenInfo) (TokenResponse, error) {
|
||||
qv := url.Values{}
|
||||
if err := addClaims(qv, authParameters); err != nil {
|
||||
return TokenResponse{}, err
|
||||
}
|
||||
qv.Set(username, authParameters.Username)
|
||||
qv.Set(password, authParameters.Password)
|
||||
qv.Set(clientID, authParameters.ClientID)
|
||||
qv.Set(clientInfo, clientInfoVal)
|
||||
qv.Set("assertion", base64.StdEncoding.WithPadding(base64.StdPadding).EncodeToString([]byte(samlGrant.Assertion)))
|
||||
addScopeQueryParam(qv, authParameters)
|
||||
|
||||
switch samlGrant.AssertionType {
|
||||
case grant.SAMLV1:
|
||||
qv.Set(grantType, grant.SAMLV1)
|
||||
case grant.SAMLV2:
|
||||
qv.Set(grantType, grant.SAMLV2)
|
||||
default:
|
||||
return TokenResponse{}, fmt.Errorf("GetAccessTokenFromSamlGrant returned unknown SAML assertion type: %q", samlGrant.AssertionType)
|
||||
}
|
||||
|
||||
return c.doTokenResp(ctx, authParameters, qv)
|
||||
}
|
||||
|
||||
func (c Client) doTokenResp(ctx context.Context, authParams authority.AuthParams, qv url.Values) (TokenResponse, error) {
|
||||
resp := TokenResponse{}
|
||||
if authParams.AuthnScheme != nil {
|
||||
trParams := authParams.AuthnScheme.TokenRequestParams()
|
||||
for k, v := range trParams {
|
||||
qv.Set(k, v)
|
||||
}
|
||||
}
|
||||
err := c.Comm.URLFormCall(ctx, authParams.Endpoints.TokenEndpoint, qv, &resp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp.ComputeScope(authParams)
|
||||
if c.testing {
|
||||
return resp, nil
|
||||
}
|
||||
return resp, resp.Validate()
|
||||
}
|
||||
|
||||
// prepURLVals returns an url.Values that sets various key/values if we are doing secrets
|
||||
// or JWT assertions.
|
||||
func prepURLVals(ctx context.Context, cc *Credential, authParams authority.AuthParams) (url.Values, error) {
|
||||
params := url.Values{}
|
||||
if cc.Secret != "" {
|
||||
params.Set("client_secret", cc.Secret)
|
||||
return params, nil
|
||||
}
|
||||
|
||||
jwt, err := cc.JWT(ctx, authParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params.Set("client_assertion", jwt)
|
||||
params.Set("client_assertion_type", grant.ClientAssertion)
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// openid required to get an id token
|
||||
// offline_access required to get a refresh token
|
||||
// profile required to get the client_info field back
|
||||
var detectDefaultScopes = map[string]bool{
|
||||
"openid": true,
|
||||
"offline_access": true,
|
||||
"profile": true,
|
||||
}
|
||||
|
||||
var defaultScopes = []string{"openid", "offline_access", "profile"}
|
||||
|
||||
func AppendDefaultScopes(authParameters authority.AuthParams) []string {
|
||||
scopes := make([]string, 0, len(authParameters.Scopes)+len(defaultScopes))
|
||||
for _, scope := range authParameters.Scopes {
|
||||
s := strings.TrimSpace(scope)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if detectDefaultScopes[scope] {
|
||||
continue
|
||||
}
|
||||
scopes = append(scopes, scope)
|
||||
}
|
||||
scopes = append(scopes, defaultScopes...)
|
||||
return scopes
|
||||
}
|
||||
|
||||
// addClaims adds client capabilities and claims from AuthParams to the given url.Values
|
||||
func addClaims(v url.Values, ap authority.AuthParams) error {
|
||||
claims, err := ap.MergeCapabilitiesAndClaims()
|
||||
if err == nil && claims != "" {
|
||||
v.Set("claims", claims)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func addScopeQueryParam(queryParams url.Values, authParameters authority.AuthParams) {
|
||||
scopes := AppendDefaultScopes(authParameters)
|
||||
queryParams.Set("scope", strings.Join(scopes, " "))
|
||||
}
|
||||
Generated
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
// Code generated by "stringer -type=AppType"; DO NOT EDIT.
|
||||
|
||||
package accesstokens
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ATUnknown-0]
|
||||
_ = x[ATPublic-1]
|
||||
_ = x[ATConfidential-2]
|
||||
}
|
||||
|
||||
const _AppType_name = "ATUnknownATPublicATConfidential"
|
||||
|
||||
var _AppType_index = [...]uint8{0, 9, 17, 31}
|
||||
|
||||
func (i AppType) String() string {
|
||||
if i < 0 || i >= AppType(len(_AppType_index)-1) {
|
||||
return "AppType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _AppType_name[_AppType_index[i]:_AppType_index[i+1]]
|
||||
}
|
||||
Generated
Vendored
+339
@@ -0,0 +1,339 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package accesstokens
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
internalTime "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
|
||||
)
|
||||
|
||||
// IDToken consists of all the information used to validate a user.
|
||||
// https://docs.microsoft.com/azure/active-directory/develop/id-tokens .
|
||||
type IDToken struct {
|
||||
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||
GivenName string `json:"given_name,omitempty"`
|
||||
FamilyName string `json:"family_name,omitempty"`
|
||||
MiddleName string `json:"middle_name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Oid string `json:"oid,omitempty"`
|
||||
TenantID string `json:"tid,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
UPN string `json:"upn,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
AlternativeID string `json:"alternative_id,omitempty"`
|
||||
Issuer string `json:"iss,omitempty"`
|
||||
Audience string `json:"aud,omitempty"`
|
||||
ExpirationTime int64 `json:"exp,omitempty"`
|
||||
IssuedAt int64 `json:"iat,omitempty"`
|
||||
NotBefore int64 `json:"nbf,omitempty"`
|
||||
RawToken string
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
var null = []byte("null")
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (i *IDToken) UnmarshalJSON(b []byte) error {
|
||||
if bytes.Equal(null, b) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Because we have a custom unmarshaler, you
|
||||
// cannot directly call json.Unmarshal here. If you do, it will call this function
|
||||
// recursively until reach our recursion limit. We have to create a new type
|
||||
// that doesn't have this method in order to use json.Unmarshal.
|
||||
type idToken2 IDToken
|
||||
|
||||
jwt := strings.Trim(string(b), `"`)
|
||||
jwtArr := strings.Split(jwt, ".")
|
||||
if len(jwtArr) < 2 {
|
||||
return errors.New("IDToken returned from server is invalid")
|
||||
}
|
||||
|
||||
jwtPart := jwtArr[1]
|
||||
jwtDecoded, err := decodeJWT(jwtPart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal IDToken, problem decoding JWT: %w", err)
|
||||
}
|
||||
|
||||
token := idToken2{}
|
||||
err = json.Unmarshal(jwtDecoded, &token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal IDToken: %w", err)
|
||||
}
|
||||
token.RawToken = jwt
|
||||
|
||||
*i = IDToken(token)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsZero indicates if the IDToken is the zero value.
|
||||
func (i IDToken) IsZero() bool {
|
||||
v := reflect.ValueOf(i)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if !field.IsZero() {
|
||||
switch field.Kind() {
|
||||
case reflect.Map, reflect.Slice:
|
||||
if field.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// LocalAccountID extracts an account's local account ID from an ID token.
|
||||
func (i IDToken) LocalAccountID() string {
|
||||
if i.Oid != "" {
|
||||
return i.Oid
|
||||
}
|
||||
return i.Subject
|
||||
}
|
||||
|
||||
// jwtDecoder is provided to allow tests to provide their own.
|
||||
var jwtDecoder = decodeJWT
|
||||
|
||||
// ClientInfo is used to create a Home Account ID for an account.
|
||||
type ClientInfo struct {
|
||||
UID string `json:"uid"`
|
||||
UTID string `json:"utid"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.s
|
||||
func (c *ClientInfo) UnmarshalJSON(b []byte) error {
|
||||
s := strings.Trim(string(b), `"`)
|
||||
// Client info may be empty in some flows, e.g. certificate exchange.
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Because we have a custom unmarshaler, you
|
||||
// cannot directly call json.Unmarshal here. If you do, it will call this function
|
||||
// recursively until reach our recursion limit. We have to create a new type
|
||||
// that doesn't have this method in order to use json.Unmarshal.
|
||||
type clientInfo2 ClientInfo
|
||||
|
||||
raw, err := jwtDecoder(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("TokenResponse client_info field had JWT decode error: %w", err)
|
||||
}
|
||||
|
||||
var c2 clientInfo2
|
||||
|
||||
err = json.Unmarshal(raw, &c2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("was unable to unmarshal decoded JWT in TokenRespone to ClientInfo: %w", err)
|
||||
}
|
||||
|
||||
*c = ClientInfo(c2)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Scopes represents scopes in a TokenResponse.
|
||||
type Scopes struct {
|
||||
Slice []string
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshal.
|
||||
func (s *Scopes) UnmarshalJSON(b []byte) error {
|
||||
str := strings.Trim(string(b), `"`)
|
||||
if len(str) == 0 {
|
||||
return nil
|
||||
}
|
||||
sl := strings.Split(str, " ")
|
||||
s.Slice = sl
|
||||
return nil
|
||||
}
|
||||
|
||||
// TokenResponse is the information that is returned from a token endpoint during a token acquisition flow.
|
||||
type TokenResponse struct {
|
||||
authority.OAuthResponseBase
|
||||
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
|
||||
FamilyID string `json:"foci"`
|
||||
IDToken IDToken `json:"id_token"`
|
||||
ClientInfo ClientInfo `json:"client_info"`
|
||||
ExpiresOn internalTime.DurationTime `json:"expires_in"`
|
||||
ExtExpiresOn internalTime.DurationTime `json:"ext_expires_in"`
|
||||
GrantedScopes Scopes `json:"scope"`
|
||||
DeclinedScopes []string // This is derived
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
|
||||
scopesComputed bool
|
||||
}
|
||||
|
||||
// ComputeScope computes the final scopes based on what was granted by the server and
|
||||
// what our AuthParams were from the authority server. Per OAuth spec, if no scopes are returned, the response should be treated as if all scopes were granted
|
||||
// This behavior can be observed in client assertion flows, but can happen at any time, this check ensures we treat
|
||||
// those special responses properly Link to spec: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||
func (tr *TokenResponse) ComputeScope(authParams authority.AuthParams) {
|
||||
if len(tr.GrantedScopes.Slice) == 0 {
|
||||
tr.GrantedScopes = Scopes{Slice: authParams.Scopes}
|
||||
} else {
|
||||
tr.DeclinedScopes = findDeclinedScopes(authParams.Scopes, tr.GrantedScopes.Slice)
|
||||
}
|
||||
tr.scopesComputed = true
|
||||
}
|
||||
|
||||
// HomeAccountID uniquely identifies the authenticated account, if any. It's "" when the token is an app token.
|
||||
func (tr *TokenResponse) HomeAccountID() string {
|
||||
id := tr.IDToken.Subject
|
||||
if uid := tr.ClientInfo.UID; uid != "" {
|
||||
utid := tr.ClientInfo.UTID
|
||||
if utid == "" {
|
||||
utid = uid
|
||||
}
|
||||
id = fmt.Sprintf("%s.%s", uid, utid)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Validate validates the TokenResponse has basic valid values. It must be called
|
||||
// after ComputeScopes() is called.
|
||||
func (tr *TokenResponse) Validate() error {
|
||||
if tr.Error != "" {
|
||||
return fmt.Errorf("%s: %s", tr.Error, tr.ErrorDescription)
|
||||
}
|
||||
|
||||
if tr.AccessToken == "" {
|
||||
return errors.New("response is missing access_token")
|
||||
}
|
||||
|
||||
if !tr.scopesComputed {
|
||||
return fmt.Errorf("TokenResponse hasn't had ScopesComputed() called")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *TokenResponse) CacheKey(authParams authority.AuthParams) string {
|
||||
if authParams.AuthorizationType == authority.ATOnBehalfOf {
|
||||
return authParams.AssertionHash()
|
||||
}
|
||||
if authParams.AuthorizationType == authority.ATClientCredentials {
|
||||
return authParams.AppKey()
|
||||
}
|
||||
if authParams.IsConfidentialClient || authParams.AuthorizationType == authority.ATRefreshToken {
|
||||
return tr.HomeAccountID()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findDeclinedScopes(requestedScopes []string, grantedScopes []string) []string {
|
||||
declined := []string{}
|
||||
grantedMap := map[string]bool{}
|
||||
for _, s := range grantedScopes {
|
||||
grantedMap[strings.ToLower(s)] = true
|
||||
}
|
||||
// Comparing the requested scopes with the granted scopes to see if there are any scopes that have been declined.
|
||||
for _, r := range requestedScopes {
|
||||
if !grantedMap[strings.ToLower(r)] {
|
||||
declined = append(declined, r)
|
||||
}
|
||||
}
|
||||
return declined
|
||||
}
|
||||
|
||||
// decodeJWT decodes a JWT and converts it to a byte array representing a JSON object
|
||||
// JWT has headers and payload base64url encoded without padding
|
||||
// https://tools.ietf.org/html/rfc7519#section-3 and
|
||||
// https://tools.ietf.org/html/rfc7515#section-2
|
||||
func decodeJWT(data string) ([]byte, error) {
|
||||
// https://tools.ietf.org/html/rfc7515#appendix-C
|
||||
return base64.RawURLEncoding.DecodeString(data)
|
||||
}
|
||||
|
||||
// RefreshToken is the JSON representation of a MSAL refresh token for encoding to storage.
|
||||
type RefreshToken struct {
|
||||
HomeAccountID string `json:"home_account_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
CredentialType string `json:"credential_type,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
FamilyID string `json:"family_id,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
UserAssertionHash string `json:"user_assertion_hash,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// NewRefreshToken is the constructor for RefreshToken.
|
||||
func NewRefreshToken(homeID, env, clientID, refreshToken, familyID string) RefreshToken {
|
||||
return RefreshToken{
|
||||
HomeAccountID: homeID,
|
||||
Environment: env,
|
||||
CredentialType: "RefreshToken",
|
||||
ClientID: clientID,
|
||||
FamilyID: familyID,
|
||||
Secret: refreshToken,
|
||||
}
|
||||
}
|
||||
|
||||
// Key outputs the key that can be used to uniquely look up this entry in a map.
|
||||
func (rt RefreshToken) Key() string {
|
||||
var fourth = rt.FamilyID
|
||||
if fourth == "" {
|
||||
fourth = rt.ClientID
|
||||
}
|
||||
|
||||
key := strings.Join(
|
||||
[]string{rt.HomeAccountID, rt.Environment, rt.CredentialType, fourth},
|
||||
shared.CacheKeySeparator,
|
||||
)
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
func (rt RefreshToken) GetSecret() string {
|
||||
return rt.Secret
|
||||
}
|
||||
|
||||
// DeviceCodeResult stores the response from the STS device code endpoint.
|
||||
type DeviceCodeResult struct {
|
||||
// UserCode is the code the user needs to provide when authentication at the verification URI.
|
||||
UserCode string
|
||||
// DeviceCode is the code used in the access token request.
|
||||
DeviceCode string
|
||||
// VerificationURL is the the URL where user can authenticate.
|
||||
VerificationURL string
|
||||
// ExpiresOn is the expiration time of device code in seconds.
|
||||
ExpiresOn time.Time
|
||||
// Interval is the interval at which the STS should be polled at.
|
||||
Interval int
|
||||
// Message is the message which should be displayed to the user.
|
||||
Message string
|
||||
// ClientID is the UUID issued by the authorization server for your application.
|
||||
ClientID string
|
||||
// Scopes is the OpenID scopes used to request access a protected API.
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
// NewDeviceCodeResult creates a DeviceCodeResult instance.
|
||||
func NewDeviceCodeResult(userCode, deviceCode, verificationURL string, expiresOn time.Time, interval int, message, clientID string, scopes []string) DeviceCodeResult {
|
||||
return DeviceCodeResult{userCode, deviceCode, verificationURL, expiresOn, interval, message, clientID, scopes}
|
||||
}
|
||||
|
||||
func (dcr DeviceCodeResult) String() string {
|
||||
return fmt.Sprintf("UserCode: (%v)\nDeviceCode: (%v)\nURL: (%v)\nMessage: (%v)\n", dcr.UserCode, dcr.DeviceCode, dcr.VerificationURL, dcr.Message)
|
||||
|
||||
}
|
||||
Generated
Vendored
+610
@@ -0,0 +1,610 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package authority
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
authorizationEndpoint = "https://%v/%v/oauth2/v2.0/authorize"
|
||||
aadInstanceDiscoveryEndpoint = "https://%v/common/discovery/instance"
|
||||
tenantDiscoveryEndpointWithRegion = "https://%s.%s/%s/v2.0/.well-known/openid-configuration"
|
||||
regionName = "REGION_NAME"
|
||||
defaultAPIVersion = "2021-10-01"
|
||||
imdsEndpoint = "http://169.254.169.254/metadata/instance/compute/location?format=text&api-version=" + defaultAPIVersion
|
||||
autoDetectRegion = "TryAutoDetect"
|
||||
AccessTokenTypeBearer = "Bearer"
|
||||
)
|
||||
|
||||
// These are various hosts that host AAD Instance discovery endpoints.
|
||||
const (
|
||||
defaultHost = "login.microsoftonline.com"
|
||||
loginMicrosoft = "login.microsoft.com"
|
||||
loginWindows = "login.windows.net"
|
||||
loginSTSWindows = "sts.windows.net"
|
||||
loginMicrosoftOnline = defaultHost
|
||||
)
|
||||
|
||||
// jsonCaller is an interface that allows us to mock the JSONCall method.
|
||||
type jsonCaller interface {
|
||||
JSONCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, body, resp interface{}) error
|
||||
}
|
||||
|
||||
var aadTrustedHostList = map[string]bool{
|
||||
"login.windows.net": true, // Microsoft Azure Worldwide - Used in validation scenarios where host is not this list
|
||||
"login.partner.microsoftonline.cn": true, // Microsoft Azure China
|
||||
"login.microsoftonline.de": true, // Microsoft Azure Blackforest
|
||||
"login-us.microsoftonline.com": true, // Microsoft Azure US Government - Legacy
|
||||
"login.microsoftonline.us": true, // Microsoft Azure US Government
|
||||
"login.microsoftonline.com": true, // Microsoft Azure Worldwide
|
||||
}
|
||||
|
||||
// TrustedHost checks if an AAD host is trusted/valid.
|
||||
func TrustedHost(host string) bool {
|
||||
if _, ok := aadTrustedHostList[host]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// OAuthResponseBase is the base JSON return message for an OAuth call.
|
||||
// This is embedded in other calls to get the base fields from every response.
|
||||
type OAuthResponseBase struct {
|
||||
Error string `json:"error"`
|
||||
SubError string `json:"suberror"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
ErrorCodes []int `json:"error_codes"`
|
||||
CorrelationID string `json:"correlation_id"`
|
||||
Claims string `json:"claims"`
|
||||
}
|
||||
|
||||
// TenantDiscoveryResponse is the tenant endpoints from the OpenID configuration endpoint.
|
||||
type TenantDiscoveryResponse struct {
|
||||
OAuthResponseBase
|
||||
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
Issuer string `json:"issuer"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// Validate validates that the response had the correct values required.
|
||||
func (r *TenantDiscoveryResponse) Validate() error {
|
||||
switch "" {
|
||||
case r.AuthorizationEndpoint:
|
||||
return errors.New("TenantDiscoveryResponse: authorize endpoint was not found in the openid configuration")
|
||||
case r.TokenEndpoint:
|
||||
return errors.New("TenantDiscoveryResponse: token endpoint was not found in the openid configuration")
|
||||
case r.Issuer:
|
||||
return errors.New("TenantDiscoveryResponse: issuer was not found in the openid configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type InstanceDiscoveryMetadata struct {
|
||||
PreferredNetwork string `json:"preferred_network"`
|
||||
PreferredCache string `json:"preferred_cache"`
|
||||
Aliases []string `json:"aliases"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
type InstanceDiscoveryResponse struct {
|
||||
TenantDiscoveryEndpoint string `json:"tenant_discovery_endpoint"`
|
||||
Metadata []InstanceDiscoveryMetadata `json:"metadata"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
//go:generate stringer -type=AuthorizeType
|
||||
|
||||
// AuthorizeType represents the type of token flow.
|
||||
type AuthorizeType int
|
||||
|
||||
// These are all the types of token flows.
|
||||
const (
|
||||
ATUnknown AuthorizeType = iota
|
||||
ATUsernamePassword
|
||||
ATWindowsIntegrated
|
||||
ATAuthCode
|
||||
ATInteractive
|
||||
ATClientCredentials
|
||||
ATDeviceCode
|
||||
ATRefreshToken
|
||||
AccountByID
|
||||
ATOnBehalfOf
|
||||
)
|
||||
|
||||
// These are all authority types
|
||||
const (
|
||||
AAD = "MSSTS"
|
||||
ADFS = "ADFS"
|
||||
DSTS = "DSTS"
|
||||
)
|
||||
|
||||
// DSTSTenant is referenced throughout multiple files, let us use a const in case we ever need to change it.
|
||||
const DSTSTenant = "7a433bfc-2514-4697-b467-e0933190487f"
|
||||
|
||||
// AuthenticationScheme is an extensibility mechanism designed to be used only by Azure Arc for proof of possession access tokens.
|
||||
type AuthenticationScheme interface {
|
||||
// Extra parameters that are added to the request to the /token endpoint.
|
||||
TokenRequestParams() map[string]string
|
||||
// Key ID of the public / private key pair used by the encryption algorithm, if any.
|
||||
// Tokens obtained by authentication schemes that use this are bound to the KeyId, i.e.
|
||||
// if a different kid is presented, the access token cannot be used.
|
||||
KeyID() string
|
||||
// Creates the access token that goes into an Authorization HTTP header.
|
||||
FormatAccessToken(accessToken string) (string, error)
|
||||
//Expected to match the token_type parameter returned by ESTS. Used to disambiguate
|
||||
// between ATs of different types (e.g. Bearer and PoP) when loading from cache etc.
|
||||
AccessTokenType() string
|
||||
}
|
||||
|
||||
// default authn scheme realizing AuthenticationScheme for "Bearer" tokens
|
||||
type BearerAuthenticationScheme struct{}
|
||||
|
||||
var bearerAuthnScheme BearerAuthenticationScheme
|
||||
|
||||
func (ba *BearerAuthenticationScheme) TokenRequestParams() map[string]string {
|
||||
return nil
|
||||
}
|
||||
func (ba *BearerAuthenticationScheme) KeyID() string {
|
||||
return ""
|
||||
}
|
||||
func (ba *BearerAuthenticationScheme) FormatAccessToken(accessToken string) (string, error) {
|
||||
return accessToken, nil
|
||||
}
|
||||
func (ba *BearerAuthenticationScheme) AccessTokenType() string {
|
||||
return AccessTokenTypeBearer
|
||||
}
|
||||
|
||||
// AuthParams represents the parameters used for authorization for token acquisition.
|
||||
type AuthParams struct {
|
||||
AuthorityInfo Info
|
||||
CorrelationID string
|
||||
Endpoints Endpoints
|
||||
ClientID string
|
||||
// Redirecturi is used for auth flows that specify a redirect URI (e.g. local server for interactive auth flow).
|
||||
Redirecturi string
|
||||
HomeAccountID string
|
||||
// Username is the user-name portion for username/password auth flow.
|
||||
Username string
|
||||
// Password is the password portion for username/password auth flow.
|
||||
Password string
|
||||
// Scopes is the list of scopes the user consents to.
|
||||
Scopes []string
|
||||
// AuthorizationType specifies the auth flow being used.
|
||||
AuthorizationType AuthorizeType
|
||||
// State is a random value used to prevent cross-site request forgery attacks.
|
||||
State string
|
||||
// CodeChallenge is derived from a code verifier and is sent in the auth request.
|
||||
CodeChallenge string
|
||||
// CodeChallengeMethod describes the method used to create the CodeChallenge.
|
||||
CodeChallengeMethod string
|
||||
// Prompt specifies the user prompt type during interactive auth.
|
||||
Prompt string
|
||||
// IsConfidentialClient specifies if it is a confidential client.
|
||||
IsConfidentialClient bool
|
||||
// SendX5C specifies if x5c claim(public key of the certificate) should be sent to STS.
|
||||
SendX5C bool
|
||||
// UserAssertion is the access token used to acquire token on behalf of user
|
||||
UserAssertion string
|
||||
// Capabilities the client will include with each token request, for example "CP1".
|
||||
// Call [NewClientCapabilities] to construct a value for this field.
|
||||
Capabilities ClientCapabilities
|
||||
// Claims required for an access token to satisfy a conditional access policy
|
||||
Claims string
|
||||
// KnownAuthorityHosts don't require metadata discovery because they're known to the user
|
||||
KnownAuthorityHosts []string
|
||||
// LoginHint is a username with which to pre-populate account selection during interactive auth
|
||||
LoginHint string
|
||||
// DomainHint is a directive that can be used to accelerate the user to their federated IdP sign-in page
|
||||
DomainHint string
|
||||
// AuthnScheme is an optional scheme for formatting access tokens
|
||||
AuthnScheme AuthenticationScheme
|
||||
}
|
||||
|
||||
// NewAuthParams creates an authorization parameters object.
|
||||
func NewAuthParams(clientID string, authorityInfo Info) AuthParams {
|
||||
return AuthParams{
|
||||
ClientID: clientID,
|
||||
AuthorityInfo: authorityInfo,
|
||||
CorrelationID: uuid.New().String(),
|
||||
AuthnScheme: &bearerAuthnScheme,
|
||||
}
|
||||
}
|
||||
|
||||
// WithTenant returns a copy of the AuthParams having the specified tenant ID. If the given
|
||||
// ID is empty, the copy is identical to the original. This function returns an error in
|
||||
// several cases:
|
||||
// - ID isn't specific (for example, it's "common")
|
||||
// - ID is non-empty and the authority doesn't support tenants (for example, it's an ADFS authority)
|
||||
// - the client is configured to authenticate only Microsoft accounts via the "consumers" endpoint
|
||||
// - the resulting authority URL is invalid
|
||||
func (p AuthParams) WithTenant(ID string) (AuthParams, error) {
|
||||
if ID == "" || ID == p.AuthorityInfo.Tenant {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
var authority string
|
||||
switch p.AuthorityInfo.AuthorityType {
|
||||
case AAD:
|
||||
if ID == "common" || ID == "consumers" || ID == "organizations" {
|
||||
return p, fmt.Errorf(`tenant ID must be a specific tenant, not "%s"`, ID)
|
||||
}
|
||||
if p.AuthorityInfo.Tenant == "consumers" {
|
||||
return p, errors.New(`client is configured to authenticate only personal Microsoft accounts, via the "consumers" endpoint`)
|
||||
}
|
||||
authority = "https://" + path.Join(p.AuthorityInfo.Host, ID)
|
||||
case ADFS:
|
||||
return p, errors.New("ADFS authority doesn't support tenants")
|
||||
case DSTS:
|
||||
return p, errors.New("dSTS authority doesn't support tenants")
|
||||
}
|
||||
|
||||
info, err := NewInfoFromAuthorityURI(authority, p.AuthorityInfo.ValidateAuthority, p.AuthorityInfo.InstanceDiscoveryDisabled)
|
||||
if err == nil {
|
||||
info.Region = p.AuthorityInfo.Region
|
||||
p.AuthorityInfo = info
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
// MergeCapabilitiesAndClaims combines client capabilities and challenge claims into a value suitable for an authentication request's "claims" parameter.
|
||||
func (p AuthParams) MergeCapabilitiesAndClaims() (string, error) {
|
||||
claims := p.Claims
|
||||
if len(p.Capabilities.asMap) > 0 {
|
||||
if claims == "" {
|
||||
// without claims the result is simply the capabilities
|
||||
return p.Capabilities.asJSON, nil
|
||||
}
|
||||
// Otherwise, merge claims and capabilties into a single JSON object.
|
||||
// We handle the claims challenge as a map because we don't know its structure.
|
||||
var challenge map[string]any
|
||||
if err := json.Unmarshal([]byte(claims), &challenge); err != nil {
|
||||
return "", fmt.Errorf(`claims must be JSON. Are they base64 encoded? json.Unmarshal returned "%v"`, err)
|
||||
}
|
||||
if err := merge(p.Capabilities.asMap, challenge); err != nil {
|
||||
return "", err
|
||||
}
|
||||
b, err := json.Marshal(challenge)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims = string(b)
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// merges a into b without overwriting b's values. Returns an error when a and b share a key for which either has a non-object value.
|
||||
func merge(a, b map[string]any) error {
|
||||
for k, av := range a {
|
||||
if bv, ok := b[k]; !ok {
|
||||
// b doesn't contain this key => simply set it to a's value
|
||||
b[k] = av
|
||||
} else {
|
||||
// b does contain this key => recursively merge a[k] into b[k], provided both are maps. If a[k] or b[k] isn't
|
||||
// a map, return an error because merging would overwrite some value in b. Errors shouldn't occur in practice
|
||||
// because the challenge will be from AAD, which knows the capabilities format.
|
||||
if A, ok := av.(map[string]any); ok {
|
||||
if B, ok := bv.(map[string]any); ok {
|
||||
return merge(A, B)
|
||||
} else {
|
||||
// b[k] isn't a map
|
||||
return errors.New("challenge claims conflict with client capabilities")
|
||||
}
|
||||
} else {
|
||||
// a[k] isn't a map
|
||||
return errors.New("challenge claims conflict with client capabilities")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClientCapabilities stores capabilities in the formats used by AuthParams.MergeCapabilitiesAndClaims.
|
||||
// [NewClientCapabilities] precomputes these representations because capabilities are static for the
|
||||
// lifetime of a client and are included with every authentication request i.e., these computations
|
||||
// always have the same result and would otherwise have to be repeated for every request.
|
||||
type ClientCapabilities struct {
|
||||
// asJSON is for the common case: adding the capabilities to an auth request with no challenge claims
|
||||
asJSON string
|
||||
// asMap is for merging the capabilities with challenge claims
|
||||
asMap map[string]any
|
||||
}
|
||||
|
||||
func NewClientCapabilities(capabilities []string) (ClientCapabilities, error) {
|
||||
c := ClientCapabilities{}
|
||||
var err error
|
||||
if len(capabilities) > 0 {
|
||||
cpbs := make([]string, len(capabilities))
|
||||
for i := 0; i < len(cpbs); i++ {
|
||||
cpbs[i] = fmt.Sprintf(`"%s"`, capabilities[i])
|
||||
}
|
||||
c.asJSON = fmt.Sprintf(`{"access_token":{"xms_cc":{"values":[%s]}}}`, strings.Join(cpbs, ","))
|
||||
// note our JSON is valid but we can't stop users breaking it with garbage like "}"
|
||||
err = json.Unmarshal([]byte(c.asJSON), &c.asMap)
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
// Info consists of information about the authority.
|
||||
type Info struct {
|
||||
Host string
|
||||
CanonicalAuthorityURI string
|
||||
AuthorityType string
|
||||
ValidateAuthority bool
|
||||
Tenant string
|
||||
Region string
|
||||
InstanceDiscoveryDisabled bool
|
||||
}
|
||||
|
||||
// NewInfoFromAuthorityURI creates an AuthorityInfo instance from the authority URL provided.
|
||||
func NewInfoFromAuthorityURI(authority string, validateAuthority bool, instanceDiscoveryDisabled bool) (Info, error) {
|
||||
|
||||
cannonicalAuthority := authority
|
||||
|
||||
// suffix authority with / if it doesn't have one
|
||||
if !strings.HasSuffix(cannonicalAuthority, "/") {
|
||||
cannonicalAuthority += "/"
|
||||
}
|
||||
|
||||
u, err := url.Parse(strings.ToLower(cannonicalAuthority))
|
||||
|
||||
if err != nil {
|
||||
return Info{}, fmt.Errorf("couldn't parse authority url: %w", err)
|
||||
}
|
||||
if u.Scheme != "https" {
|
||||
return Info{}, errors.New("authority url scheme must be https")
|
||||
}
|
||||
|
||||
pathParts := strings.Split(u.EscapedPath(), "/")
|
||||
if len(pathParts) < 3 {
|
||||
return Info{}, errors.New(`authority must be an URL such as "https://login.microsoftonline.com/<your tenant>"`)
|
||||
}
|
||||
|
||||
authorityType := AAD
|
||||
tenant := pathParts[1]
|
||||
switch tenant {
|
||||
case "adfs":
|
||||
authorityType = ADFS
|
||||
case "dstsv2":
|
||||
if len(pathParts) != 4 {
|
||||
return Info{}, fmt.Errorf("dSTS authority must be an https URL such as https://<authority>/dstsv2/%s", DSTSTenant)
|
||||
}
|
||||
if pathParts[2] != DSTSTenant {
|
||||
return Info{}, fmt.Errorf("dSTS authority only accepts a single tenant %q", DSTSTenant)
|
||||
}
|
||||
authorityType = DSTS
|
||||
tenant = DSTSTenant
|
||||
}
|
||||
|
||||
// u.Host includes the port, if any, which is required for private cloud deployments
|
||||
return Info{
|
||||
Host: u.Host,
|
||||
CanonicalAuthorityURI: cannonicalAuthority,
|
||||
AuthorityType: authorityType,
|
||||
ValidateAuthority: validateAuthority,
|
||||
Tenant: tenant,
|
||||
InstanceDiscoveryDisabled: instanceDiscoveryDisabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Endpoints consists of the endpoints from the tenant discovery response.
|
||||
type Endpoints struct {
|
||||
AuthorizationEndpoint string
|
||||
TokenEndpoint string
|
||||
selfSignedJwtAudience string
|
||||
authorityHost string
|
||||
}
|
||||
|
||||
// NewEndpoints creates an Endpoints object.
|
||||
func NewEndpoints(authorizationEndpoint string, tokenEndpoint string, selfSignedJwtAudience string, authorityHost string) Endpoints {
|
||||
return Endpoints{authorizationEndpoint, tokenEndpoint, selfSignedJwtAudience, authorityHost}
|
||||
}
|
||||
|
||||
// UserRealmAccountType refers to the type of user realm.
|
||||
type UserRealmAccountType string
|
||||
|
||||
// These are the different types of user realms.
|
||||
const (
|
||||
Unknown UserRealmAccountType = ""
|
||||
Federated UserRealmAccountType = "Federated"
|
||||
Managed UserRealmAccountType = "Managed"
|
||||
)
|
||||
|
||||
// UserRealm is used for the username password request to determine user type
|
||||
type UserRealm struct {
|
||||
AccountType UserRealmAccountType `json:"account_type"`
|
||||
DomainName string `json:"domain_name"`
|
||||
CloudInstanceName string `json:"cloud_instance_name"`
|
||||
CloudAudienceURN string `json:"cloud_audience_urn"`
|
||||
|
||||
// required if accountType is Federated
|
||||
FederationProtocol string `json:"federation_protocol"`
|
||||
FederationMetadataURL string `json:"federation_metadata_url"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
func (u UserRealm) validate() error {
|
||||
switch "" {
|
||||
case string(u.AccountType):
|
||||
return errors.New("the account type (Federated or Managed) is missing")
|
||||
case u.DomainName:
|
||||
return errors.New("domain name of user realm is missing")
|
||||
case u.CloudInstanceName:
|
||||
return errors.New("cloud instance name of user realm is missing")
|
||||
case u.CloudAudienceURN:
|
||||
return errors.New("cloud Instance URN is missing")
|
||||
}
|
||||
|
||||
if u.AccountType == Federated {
|
||||
switch "" {
|
||||
case u.FederationProtocol:
|
||||
return errors.New("federation protocol of user realm is missing")
|
||||
case u.FederationMetadataURL:
|
||||
return errors.New("federation metadata URL of user realm is missing")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Client represents the REST calls to authority backends.
|
||||
type Client struct {
|
||||
// Comm provides the HTTP transport client.
|
||||
Comm jsonCaller // *comm.Client
|
||||
}
|
||||
|
||||
func (c Client) UserRealm(ctx context.Context, authParams AuthParams) (UserRealm, error) {
|
||||
endpoint := fmt.Sprintf("https://%s/common/UserRealm/%s", authParams.Endpoints.authorityHost, url.PathEscape(authParams.Username))
|
||||
qv := url.Values{
|
||||
"api-version": []string{"1.0"},
|
||||
}
|
||||
|
||||
resp := UserRealm{}
|
||||
err := c.Comm.JSONCall(
|
||||
ctx,
|
||||
endpoint,
|
||||
http.Header{"client-request-id": []string{authParams.CorrelationID}},
|
||||
qv,
|
||||
nil,
|
||||
&resp,
|
||||
)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, resp.validate()
|
||||
}
|
||||
|
||||
func (c Client) GetTenantDiscoveryResponse(ctx context.Context, openIDConfigurationEndpoint string) (TenantDiscoveryResponse, error) {
|
||||
resp := TenantDiscoveryResponse{}
|
||||
err := c.Comm.JSONCall(
|
||||
ctx,
|
||||
openIDConfigurationEndpoint,
|
||||
http.Header{},
|
||||
nil,
|
||||
nil,
|
||||
&resp,
|
||||
)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// AADInstanceDiscovery attempts to discover a tenant endpoint (used in OIDC auth with an authorization endpoint).
|
||||
// This is done by AAD which allows for aliasing of tenants (windows.sts.net is the same as login.windows.com).
|
||||
func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (InstanceDiscoveryResponse, error) {
|
||||
region := ""
|
||||
var err error
|
||||
resp := InstanceDiscoveryResponse{}
|
||||
if authorityInfo.Region != "" && authorityInfo.Region != autoDetectRegion {
|
||||
region = authorityInfo.Region
|
||||
} else if authorityInfo.Region == autoDetectRegion {
|
||||
region = detectRegion(ctx)
|
||||
}
|
||||
if region != "" {
|
||||
environment := authorityInfo.Host
|
||||
switch environment {
|
||||
case loginMicrosoft, loginWindows, loginSTSWindows, defaultHost:
|
||||
environment = loginMicrosoft
|
||||
}
|
||||
|
||||
resp.TenantDiscoveryEndpoint = fmt.Sprintf(tenantDiscoveryEndpointWithRegion, region, environment, authorityInfo.Tenant)
|
||||
metadata := InstanceDiscoveryMetadata{
|
||||
PreferredNetwork: fmt.Sprintf("%v.%v", region, authorityInfo.Host),
|
||||
PreferredCache: authorityInfo.Host,
|
||||
Aliases: []string{fmt.Sprintf("%v.%v", region, authorityInfo.Host), authorityInfo.Host},
|
||||
}
|
||||
resp.Metadata = []InstanceDiscoveryMetadata{metadata}
|
||||
} else {
|
||||
qv := url.Values{}
|
||||
qv.Set("api-version", "1.1")
|
||||
qv.Set("authorization_endpoint", fmt.Sprintf(authorizationEndpoint, authorityInfo.Host, authorityInfo.Tenant))
|
||||
|
||||
discoveryHost := defaultHost
|
||||
if TrustedHost(authorityInfo.Host) {
|
||||
discoveryHost = authorityInfo.Host
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf(aadInstanceDiscoveryEndpoint, discoveryHost)
|
||||
err = c.Comm.JSONCall(ctx, endpoint, http.Header{}, qv, nil, &resp)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func detectRegion(ctx context.Context) string {
|
||||
region := os.Getenv(regionName)
|
||||
if region != "" {
|
||||
region = strings.ReplaceAll(region, " ", "")
|
||||
return strings.ToLower(region)
|
||||
}
|
||||
// HTTP call to IMDS endpoint to get region
|
||||
// Refer : https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=%2FPinAuthToRegion%2FAAD%20SDK%20Proposal%20to%20Pin%20Auth%20to%20region.md&_a=preview&version=GBdev
|
||||
// Set a 2 second timeout for this http client which only does calls to IMDS endpoint
|
||||
client := http.Client{
|
||||
Timeout: time.Duration(2 * time.Second),
|
||||
}
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imdsEndpoint, nil)
|
||||
req.Header.Set("Metadata", "true")
|
||||
resp, err := client.Do(req)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
// If the request times out or there is an error, it is retried once
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
resp, err = client.Do(req)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
response, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(response)
|
||||
}
|
||||
|
||||
func (a *AuthParams) CacheKey(isAppCache bool) string {
|
||||
if a.AuthorizationType == ATOnBehalfOf {
|
||||
return a.AssertionHash()
|
||||
}
|
||||
if a.AuthorizationType == ATClientCredentials || isAppCache {
|
||||
return a.AppKey()
|
||||
}
|
||||
if a.AuthorizationType == ATRefreshToken || a.AuthorizationType == AccountByID {
|
||||
return a.HomeAccountID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (a *AuthParams) AssertionHash() string {
|
||||
hasher := sha256.New()
|
||||
// Per documentation this never returns an error : https://pkg.go.dev/hash#pkg-types
|
||||
_, _ = hasher.Write([]byte(a.UserAssertion))
|
||||
sha := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
|
||||
return sha
|
||||
}
|
||||
|
||||
func (a *AuthParams) AppKey() string {
|
||||
if a.AuthorityInfo.Tenant != "" {
|
||||
return fmt.Sprintf("%s_%s_AppTokenCache", a.ClientID, a.AuthorityInfo.Tenant)
|
||||
}
|
||||
return fmt.Sprintf("%s__AppTokenCache", a.ClientID)
|
||||
}
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
// Code generated by "stringer -type=AuthorizeType"; DO NOT EDIT.
|
||||
|
||||
package authority
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ATUnknown-0]
|
||||
_ = x[ATUsernamePassword-1]
|
||||
_ = x[ATWindowsIntegrated-2]
|
||||
_ = x[ATAuthCode-3]
|
||||
_ = x[ATInteractive-4]
|
||||
_ = x[ATClientCredentials-5]
|
||||
_ = x[ATDeviceCode-6]
|
||||
_ = x[ATRefreshToken-7]
|
||||
}
|
||||
|
||||
const _AuthorizeType_name = "ATUnknownATUsernamePasswordATWindowsIntegratedATAuthCodeATInteractiveATClientCredentialsATDeviceCodeATRefreshToken"
|
||||
|
||||
var _AuthorizeType_index = [...]uint8{0, 9, 27, 46, 56, 69, 88, 100, 114}
|
||||
|
||||
func (i AuthorizeType) String() string {
|
||||
if i < 0 || i >= AuthorizeType(len(_AuthorizeType_index)-1) {
|
||||
return "AuthorizeType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _AuthorizeType_name[_AuthorizeType_index[i]:_AuthorizeType_index[i+1]]
|
||||
}
|
||||
Generated
Vendored
+319
@@ -0,0 +1,319 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package comm provides helpers for communicating with HTTP backends.
|
||||
package comm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
|
||||
customJSON "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version"
|
||||
)
|
||||
|
||||
// HTTPClient represents an HTTP client.
|
||||
// It's usually an *http.Client from the standard library.
|
||||
type HTTPClient interface {
|
||||
// Do sends an HTTP request and returns an HTTP response.
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
|
||||
// CloseIdleConnections closes any idle connections in a "keep-alive" state.
|
||||
CloseIdleConnections()
|
||||
}
|
||||
|
||||
// Client provides a wrapper to our *http.Client that handles compression and serialization needs.
|
||||
type Client struct {
|
||||
client HTTPClient
|
||||
}
|
||||
|
||||
// New returns a new Client object.
|
||||
func New(httpClient HTTPClient) *Client {
|
||||
if httpClient == nil {
|
||||
panic("http.Client cannot == nil")
|
||||
}
|
||||
|
||||
return &Client{client: httpClient}
|
||||
}
|
||||
|
||||
// JSONCall connects to the REST endpoint passing the HTTP query values, headers and JSON conversion
|
||||
// of body in the HTTP body. It automatically handles compression and decompression with gzip. The response is JSON
|
||||
// unmarshalled into resp. resp must be a pointer to a struct. If the body struct contains a field called
|
||||
// "AdditionalFields" we use a custom marshal/unmarshal engine.
|
||||
func (c *Client) JSONCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, body, resp interface{}) error {
|
||||
if qv == nil {
|
||||
qv = url.Values{}
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(resp)
|
||||
if err := c.checkResp(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Choose a JSON marshal/unmarshal depending on if we have AdditionalFields attribute.
|
||||
var marshal = json.Marshal
|
||||
var unmarshal = json.Unmarshal
|
||||
if _, ok := v.Elem().Type().FieldByName("AdditionalFields"); ok {
|
||||
marshal = customJSON.Marshal
|
||||
unmarshal = customJSON.Unmarshal
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s?%s", endpoint, qv.Encode()), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create request: %w", err)
|
||||
}
|
||||
|
||||
addStdHeaders(headers)
|
||||
req.Header = headers
|
||||
|
||||
if body != nil {
|
||||
// Note: In case your wondering why we are not gzip encoding....
|
||||
// I'm not sure if these various services support gzip on send.
|
||||
headers.Add("Content-Type", "application/json; charset=utf-8")
|
||||
data, err := marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bug: conn.Call(): could not marshal the body object: %w", err)
|
||||
}
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(data))
|
||||
req.Method = http.MethodPost
|
||||
}
|
||||
|
||||
data, err := c.do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
if err := unmarshal(data, resp); err != nil {
|
||||
return fmt.Errorf("json decode error: %w\njson message bytes were: %s", err, string(data))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XMLCall connects to an endpoint and decodes the XML response into resp. This is used when
|
||||
// sending application/xml . If sending XML via SOAP, use SOAPCall().
|
||||
func (c *Client) XMLCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, resp interface{}) error {
|
||||
if err := c.checkResp(reflect.ValueOf(resp)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if qv == nil {
|
||||
qv = url.Values{}
|
||||
}
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse path URL(%s): %w", endpoint, err)
|
||||
}
|
||||
u.RawQuery = qv.Encode()
|
||||
|
||||
headers.Set("Content-Type", "application/xml; charset=utf-8") // This was not set in he original Mex(), but...
|
||||
addStdHeaders(headers)
|
||||
|
||||
return c.xmlCall(ctx, u, headers, "", resp)
|
||||
}
|
||||
|
||||
// SOAPCall returns the SOAP message given an endpoint, action, body of the request and the response object to marshal into.
|
||||
func (c *Client) SOAPCall(ctx context.Context, endpoint, action string, headers http.Header, qv url.Values, body string, resp interface{}) error {
|
||||
if body == "" {
|
||||
return fmt.Errorf("cannot make a SOAP call with body set to empty string")
|
||||
}
|
||||
|
||||
if err := c.checkResp(reflect.ValueOf(resp)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if qv == nil {
|
||||
qv = url.Values{}
|
||||
}
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse path URL(%s): %w", endpoint, err)
|
||||
}
|
||||
u.RawQuery = qv.Encode()
|
||||
|
||||
headers.Set("Content-Type", "application/soap+xml; charset=utf-8")
|
||||
headers.Set("SOAPAction", action)
|
||||
addStdHeaders(headers)
|
||||
|
||||
return c.xmlCall(ctx, u, headers, body, resp)
|
||||
}
|
||||
|
||||
// xmlCall sends an XML in body and decodes into resp. This simply does the transport and relies on
|
||||
// an upper level call to set things such as SOAP parameters and Content-Type, if required.
|
||||
func (c *Client) xmlCall(ctx context.Context, u *url.URL, headers http.Header, body string, resp interface{}) error {
|
||||
req := &http.Request{Method: http.MethodGet, URL: u, Header: headers}
|
||||
|
||||
if len(body) > 0 {
|
||||
req.Method = http.MethodPost
|
||||
req.Body = io.NopCloser(strings.NewReader(body))
|
||||
}
|
||||
|
||||
data, err := c.do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return xml.Unmarshal(data, resp)
|
||||
}
|
||||
|
||||
// URLFormCall is used to make a call where we need to send application/x-www-form-urlencoded data
|
||||
// to the backend and receive JSON back. qv will be encoded into the request body.
|
||||
func (c *Client) URLFormCall(ctx context.Context, endpoint string, qv url.Values, resp interface{}) error {
|
||||
if len(qv) == 0 {
|
||||
return fmt.Errorf("URLFormCall() requires qv to have non-zero length")
|
||||
}
|
||||
|
||||
if err := c.checkResp(reflect.ValueOf(resp)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse path URL(%s): %w", endpoint, err)
|
||||
}
|
||||
|
||||
headers := http.Header{}
|
||||
headers.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
addStdHeaders(headers)
|
||||
|
||||
enc := qv.Encode()
|
||||
|
||||
req := &http.Request{
|
||||
Method: http.MethodPost,
|
||||
URL: u,
|
||||
Header: headers,
|
||||
ContentLength: int64(len(enc)),
|
||||
Body: io.NopCloser(strings.NewReader(enc)),
|
||||
GetBody: func() (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader(enc)), nil
|
||||
},
|
||||
}
|
||||
|
||||
data, err := c.do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(resp)
|
||||
if err := c.checkResp(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var unmarshal = json.Unmarshal
|
||||
if _, ok := v.Elem().Type().FieldByName("AdditionalFields"); ok {
|
||||
unmarshal = customJSON.Unmarshal
|
||||
}
|
||||
if resp != nil {
|
||||
if err := unmarshal(data, resp); err != nil {
|
||||
return fmt.Errorf("json decode error: %w\nraw message was: %s", err, string(data))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// do makes the HTTP call to the server and returns the contents of the body.
|
||||
func (c *Client) do(ctx context.Context, req *http.Request) ([]byte, error) {
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
reply, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("server response error:\n %w", err)
|
||||
}
|
||||
defer reply.Body.Close()
|
||||
|
||||
data, err := c.readBody(reply)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read the body of an HTTP Response: %w", err)
|
||||
}
|
||||
reply.Body = io.NopCloser(bytes.NewBuffer(data))
|
||||
|
||||
// NOTE: This doesn't happen immediately after the call so that we can get an error message
|
||||
// from the server and include it in our error.
|
||||
switch reply.StatusCode {
|
||||
case 200, 201:
|
||||
default:
|
||||
sd := strings.TrimSpace(string(data))
|
||||
if sd != "" {
|
||||
// We probably have the error in the body.
|
||||
return nil, errors.CallErr{
|
||||
Req: req,
|
||||
Resp: reply,
|
||||
Err: fmt.Errorf("http call(%s)(%s) error: reply status code was %d:\n%s", req.URL.String(), req.Method, reply.StatusCode, sd),
|
||||
}
|
||||
}
|
||||
return nil, errors.CallErr{
|
||||
Req: req,
|
||||
Resp: reply,
|
||||
Err: fmt.Errorf("http call(%s)(%s) error: reply status code was %d", req.URL.String(), req.Method, reply.StatusCode),
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// checkResp checks a response object o make sure it is a pointer to a struct.
|
||||
func (c *Client) checkResp(v reflect.Value) error {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("bug: resp argument must a *struct, was %T", v.Interface())
|
||||
}
|
||||
v = v.Elem()
|
||||
if v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("bug: resp argument must be a *struct, was %T", v.Interface())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readBody reads the body out of an *http.Response. It supports gzip encoded responses.
|
||||
func (c *Client) readBody(resp *http.Response) ([]byte, error) {
|
||||
var reader io.Reader = resp.Body
|
||||
switch resp.Header.Get("Content-Encoding") {
|
||||
case "":
|
||||
// Do nothing
|
||||
case "gzip":
|
||||
reader = gzipDecompress(resp.Body)
|
||||
default:
|
||||
return nil, fmt.Errorf("bug: comm.Client.JSONCall(): content was send with unsupported content-encoding %s", resp.Header.Get("Content-Encoding"))
|
||||
}
|
||||
return io.ReadAll(reader)
|
||||
}
|
||||
|
||||
var testID string
|
||||
|
||||
// addStdHeaders adds the standard headers we use on all calls.
|
||||
func addStdHeaders(headers http.Header) http.Header {
|
||||
headers.Set("Accept-Encoding", "gzip")
|
||||
// So that I can have a static id for tests.
|
||||
if testID != "" {
|
||||
headers.Set("client-request-id", testID)
|
||||
headers.Set("Return-Client-Request-Id", "false")
|
||||
} else {
|
||||
headers.Set("client-request-id", uuid.New().String())
|
||||
headers.Set("Return-Client-Request-Id", "false")
|
||||
}
|
||||
headers.Set("x-client-sku", "MSAL.Go")
|
||||
headers.Set("x-client-os", runtime.GOOS)
|
||||
headers.Set("x-client-cpu", runtime.GOARCH)
|
||||
headers.Set("x-client-ver", version.Version)
|
||||
return headers
|
||||
}
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package comm
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
)
|
||||
|
||||
func gzipDecompress(r io.Reader) io.Reader {
|
||||
gzipReader, _ := gzip.NewReader(r)
|
||||
|
||||
pipeOut, pipeIn := io.Pipe()
|
||||
go func() {
|
||||
// decompression bomb would have to come from Azure services.
|
||||
// If we want to limit, we should do that in comm.do().
|
||||
_, err := io.Copy(pipeIn, gzipReader) //nolint
|
||||
if err != nil {
|
||||
// don't need the error.
|
||||
pipeIn.CloseWithError(err) //nolint
|
||||
gzipReader.Close()
|
||||
return
|
||||
}
|
||||
if err := gzipReader.Close(); err != nil {
|
||||
// don't need the error.
|
||||
pipeIn.CloseWithError(err) //nolint
|
||||
return
|
||||
}
|
||||
pipeIn.Close()
|
||||
}()
|
||||
return pipeOut
|
||||
}
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package grant holds types of grants issued by authorization services.
|
||||
package grant
|
||||
|
||||
const (
|
||||
Password = "password"
|
||||
JWT = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
SAMLV1 = "urn:ietf:params:oauth:grant-type:saml1_1-bearer"
|
||||
SAMLV2 = "urn:ietf:params:oauth:grant-type:saml2-bearer"
|
||||
DeviceCode = "device_code"
|
||||
AuthCode = "authorization_code"
|
||||
RefreshToken = "refresh_token"
|
||||
ClientCredential = "client_credentials"
|
||||
ClientAssertion = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
||||
)
|
||||
Generated
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package ops provides operations to various backend services using REST clients.
|
||||
|
||||
The REST type provides several clients that can be used to communicate to backends.
|
||||
Usage is simple:
|
||||
|
||||
rest := ops.New()
|
||||
|
||||
// Creates an authority client and calls the UserRealm() method.
|
||||
userRealm, err := rest.Authority().UserRealm(ctx, authParameters)
|
||||
if err != nil {
|
||||
// Do something
|
||||
}
|
||||
*/
|
||||
package ops
|
||||
|
||||
import (
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust"
|
||||
)
|
||||
|
||||
// HTTPClient represents an HTTP client.
|
||||
// It's usually an *http.Client from the standard library.
|
||||
type HTTPClient = comm.HTTPClient
|
||||
|
||||
// REST provides REST clients for communicating with various backends used by MSAL.
|
||||
type REST struct {
|
||||
client *comm.Client
|
||||
}
|
||||
|
||||
// New is the constructor for REST.
|
||||
func New(httpClient HTTPClient) *REST {
|
||||
return &REST{client: comm.New(httpClient)}
|
||||
}
|
||||
|
||||
// Authority returns a client for querying information about various authorities.
|
||||
func (r *REST) Authority() authority.Client {
|
||||
return authority.Client{Comm: r.client}
|
||||
}
|
||||
|
||||
// AccessTokens returns a client that can be used to get various access tokens for
|
||||
// authorization purposes.
|
||||
func (r *REST) AccessTokens() accesstokens.Client {
|
||||
return accesstokens.Client{Comm: r.client}
|
||||
}
|
||||
|
||||
// WSTrust provides access to various metadata in a WSTrust service. This data can
|
||||
// be used to gain tokens based on SAML data using the client provided by AccessTokens().
|
||||
func (r *REST) WSTrust() wstrust.Client {
|
||||
return wstrust.Client{Comm: r.client}
|
||||
}
|
||||
Generated
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
// Code generated by "stringer -type=endpointType"; DO NOT EDIT.
|
||||
|
||||
package defs
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[etUnknown-0]
|
||||
_ = x[etUsernamePassword-1]
|
||||
_ = x[etWindowsTransport-2]
|
||||
}
|
||||
|
||||
const _endpointType_name = "etUnknownetUsernamePasswordetWindowsTransport"
|
||||
|
||||
var _endpointType_index = [...]uint8{0, 9, 27, 45}
|
||||
|
||||
func (i endpointType) String() string {
|
||||
if i < 0 || i >= endpointType(len(_endpointType_index)-1) {
|
||||
return "endpointType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _endpointType_name[_endpointType_index[i]:_endpointType_index[i+1]]
|
||||
}
|
||||
Generated
Vendored
+394
@@ -0,0 +1,394 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package defs
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type Definitions struct {
|
||||
XMLName xml.Name `xml:"definitions"`
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
TargetNamespace string `xml:"targetNamespace,attr"`
|
||||
WSDL string `xml:"wsdl,attr"`
|
||||
XSD string `xml:"xsd,attr"`
|
||||
T string `xml:"t,attr"`
|
||||
SOAPENC string `xml:"soapenc,attr"`
|
||||
SOAP string `xml:"soap,attr"`
|
||||
TNS string `xml:"tns,attr"`
|
||||
MSC string `xml:"msc,attr"`
|
||||
WSAM string `xml:"wsam,attr"`
|
||||
SOAP12 string `xml:"soap12,attr"`
|
||||
WSA10 string `xml:"wsa10,attr"`
|
||||
WSA string `xml:"wsa,attr"`
|
||||
WSAW string `xml:"wsaw,attr"`
|
||||
WSX string `xml:"wsx,attr"`
|
||||
WSAP string `xml:"wsap,attr"`
|
||||
WSU string `xml:"wsu,attr"`
|
||||
Trust string `xml:"trust,attr"`
|
||||
WSP string `xml:"wsp,attr"`
|
||||
Policy []Policy `xml:"Policy"`
|
||||
Types Types `xml:"types"`
|
||||
Message []Message `xml:"message"`
|
||||
PortType []PortType `xml:"portType"`
|
||||
Binding []Binding `xml:"binding"`
|
||||
Service Service `xml:"service"`
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
Text string `xml:",chardata"`
|
||||
ID string `xml:"Id,attr"`
|
||||
ExactlyOne ExactlyOne `xml:"ExactlyOne"`
|
||||
}
|
||||
|
||||
type ExactlyOne struct {
|
||||
Text string `xml:",chardata"`
|
||||
All All `xml:"All"`
|
||||
}
|
||||
|
||||
type All struct {
|
||||
Text string `xml:",chardata"`
|
||||
NegotiateAuthentication NegotiateAuthentication `xml:"NegotiateAuthentication"`
|
||||
TransportBinding TransportBinding `xml:"TransportBinding"`
|
||||
UsingAddressing Text `xml:"UsingAddressing"`
|
||||
EndorsingSupportingTokens EndorsingSupportingTokens `xml:"EndorsingSupportingTokens"`
|
||||
WSS11 WSS11 `xml:"Wss11"`
|
||||
Trust10 Trust10 `xml:"Trust10"`
|
||||
SignedSupportingTokens SignedSupportingTokens `xml:"SignedSupportingTokens"`
|
||||
Trust13 WSTrust13 `xml:"Trust13"`
|
||||
SignedEncryptedSupportingTokens SignedEncryptedSupportingTokens `xml:"SignedEncryptedSupportingTokens"`
|
||||
}
|
||||
|
||||
type NegotiateAuthentication struct {
|
||||
Text string `xml:",chardata"`
|
||||
HTTP string `xml:"http,attr"`
|
||||
XMLName xml.Name
|
||||
}
|
||||
|
||||
type TransportBinding struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy TransportBindingPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type TransportBindingPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
TransportToken TransportToken `xml:"TransportToken"`
|
||||
AlgorithmSuite AlgorithmSuite `xml:"AlgorithmSuite"`
|
||||
Layout Layout `xml:"Layout"`
|
||||
IncludeTimestamp Text `xml:"IncludeTimestamp"`
|
||||
}
|
||||
|
||||
type TransportToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
Policy TransportTokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type TransportTokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
HTTPSToken HTTPSToken `xml:"HttpsToken"`
|
||||
}
|
||||
|
||||
type HTTPSToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequireClientCertificate string `xml:"RequireClientCertificate,attr"`
|
||||
}
|
||||
|
||||
type AlgorithmSuite struct {
|
||||
Text string `xml:",chardata"`
|
||||
Policy AlgorithmSuitePolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type AlgorithmSuitePolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
Basic256 Text `xml:"Basic256"`
|
||||
Basic128 Text `xml:"Basic128"`
|
||||
}
|
||||
|
||||
type Layout struct {
|
||||
Text string `xml:",chardata"`
|
||||
Policy LayoutPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type LayoutPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
Strict Text `xml:"Strict"`
|
||||
}
|
||||
|
||||
type EndorsingSupportingTokens struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy EndorsingSupportingTokensPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type EndorsingSupportingTokensPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
X509Token X509Token `xml:"X509Token"`
|
||||
RSAToken RSAToken `xml:"RsaToken"`
|
||||
SignedParts SignedParts `xml:"SignedParts"`
|
||||
KerberosToken KerberosToken `xml:"KerberosToken"`
|
||||
IssuedToken IssuedToken `xml:"IssuedToken"`
|
||||
KeyValueToken KeyValueToken `xml:"KeyValueToken"`
|
||||
}
|
||||
|
||||
type X509Token struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Policy X509TokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type X509TokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequireThumbprintReference Text `xml:"RequireThumbprintReference"`
|
||||
WSSX509V3Token10 Text `xml:"WssX509V3Token10"`
|
||||
}
|
||||
|
||||
type RSAToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Optional string `xml:"Optional,attr"`
|
||||
MSSP string `xml:"mssp,attr"`
|
||||
}
|
||||
|
||||
type SignedParts struct {
|
||||
Text string `xml:",chardata"`
|
||||
Header SignedPartsHeader `xml:"Header"`
|
||||
}
|
||||
|
||||
type SignedPartsHeader struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"Name,attr"`
|
||||
Namespace string `xml:"Namespace,attr"`
|
||||
}
|
||||
|
||||
type KerberosToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Policy KerberosTokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type KerberosTokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
WSSGSSKerberosV5ApReqToken11 Text `xml:"WssGssKerberosV5ApReqToken11"`
|
||||
}
|
||||
|
||||
type IssuedToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
RequestSecurityTokenTemplate RequestSecurityTokenTemplate `xml:"RequestSecurityTokenTemplate"`
|
||||
Policy IssuedTokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type RequestSecurityTokenTemplate struct {
|
||||
Text string `xml:",chardata"`
|
||||
KeyType Text `xml:"KeyType"`
|
||||
EncryptWith Text `xml:"EncryptWith"`
|
||||
SignatureAlgorithm Text `xml:"SignatureAlgorithm"`
|
||||
CanonicalizationAlgorithm Text `xml:"CanonicalizationAlgorithm"`
|
||||
EncryptionAlgorithm Text `xml:"EncryptionAlgorithm"`
|
||||
KeySize Text `xml:"KeySize"`
|
||||
KeyWrapAlgorithm Text `xml:"KeyWrapAlgorithm"`
|
||||
}
|
||||
|
||||
type IssuedTokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequireInternalReference Text `xml:"RequireInternalReference"`
|
||||
}
|
||||
|
||||
type KeyValueToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Optional string `xml:"Optional,attr"`
|
||||
}
|
||||
|
||||
type WSS11 struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy Wss11Policy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type Wss11Policy struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustSupportRefThumbprint Text `xml:"MustSupportRefThumbprint"`
|
||||
}
|
||||
|
||||
type Trust10 struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy Trust10Policy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type Trust10Policy struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustSupportIssuedTokens Text `xml:"MustSupportIssuedTokens"`
|
||||
RequireClientEntropy Text `xml:"RequireClientEntropy"`
|
||||
RequireServerEntropy Text `xml:"RequireServerEntropy"`
|
||||
}
|
||||
|
||||
type SignedSupportingTokens struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy SupportingTokensPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type SupportingTokensPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
UsernameToken UsernameToken `xml:"UsernameToken"`
|
||||
}
|
||||
type UsernameToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
IncludeToken string `xml:"IncludeToken,attr"`
|
||||
Policy UsernameTokenPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type UsernameTokenPolicy struct {
|
||||
Text string `xml:",chardata"`
|
||||
WSSUsernameToken10 WSSUsernameToken10 `xml:"WssUsernameToken10"`
|
||||
}
|
||||
|
||||
type WSSUsernameToken10 struct {
|
||||
Text string `xml:",chardata"`
|
||||
XMLName xml.Name
|
||||
}
|
||||
|
||||
type WSTrust13 struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy WSTrust13Policy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type WSTrust13Policy struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustSupportIssuedTokens Text `xml:"MustSupportIssuedTokens"`
|
||||
RequireClientEntropy Text `xml:"RequireClientEntropy"`
|
||||
RequireServerEntropy Text `xml:"RequireServerEntropy"`
|
||||
}
|
||||
|
||||
type SignedEncryptedSupportingTokens struct {
|
||||
Text string `xml:",chardata"`
|
||||
SP string `xml:"sp,attr"`
|
||||
Policy SupportingTokensPolicy `xml:"Policy"`
|
||||
}
|
||||
|
||||
type Types struct {
|
||||
Text string `xml:",chardata"`
|
||||
Schema Schema `xml:"schema"`
|
||||
}
|
||||
|
||||
type Schema struct {
|
||||
Text string `xml:",chardata"`
|
||||
TargetNamespace string `xml:"targetNamespace,attr"`
|
||||
Import []Import `xml:"import"`
|
||||
}
|
||||
|
||||
type Import struct {
|
||||
Text string `xml:",chardata"`
|
||||
SchemaLocation string `xml:"schemaLocation,attr"`
|
||||
Namespace string `xml:"namespace,attr"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Part Part `xml:"part"`
|
||||
}
|
||||
|
||||
type Part struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Element string `xml:"element,attr"`
|
||||
}
|
||||
|
||||
type PortType struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Operation Operation `xml:"operation"`
|
||||
}
|
||||
|
||||
type Operation struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Input OperationIO `xml:"input"`
|
||||
Output OperationIO `xml:"output"`
|
||||
}
|
||||
|
||||
type OperationIO struct {
|
||||
Text string `xml:",chardata"`
|
||||
Action string `xml:"Action,attr"`
|
||||
Message string `xml:"message,attr"`
|
||||
Body OperationIOBody `xml:"body"`
|
||||
}
|
||||
|
||||
type OperationIOBody struct {
|
||||
Text string `xml:",chardata"`
|
||||
Use string `xml:"use,attr"`
|
||||
}
|
||||
|
||||
type Binding struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
PolicyReference PolicyReference `xml:"PolicyReference"`
|
||||
Binding DefinitionsBinding `xml:"binding"`
|
||||
Operation BindingOperation `xml:"operation"`
|
||||
}
|
||||
|
||||
type PolicyReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
URI string `xml:"URI,attr"`
|
||||
}
|
||||
|
||||
type DefinitionsBinding struct {
|
||||
Text string `xml:",chardata"`
|
||||
Transport string `xml:"transport,attr"`
|
||||
}
|
||||
|
||||
type BindingOperation struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Operation BindingOperationOperation `xml:"operation"`
|
||||
Input BindingOperationIO `xml:"input"`
|
||||
Output BindingOperationIO `xml:"output"`
|
||||
}
|
||||
|
||||
type BindingOperationOperation struct {
|
||||
Text string `xml:",chardata"`
|
||||
SoapAction string `xml:"soapAction,attr"`
|
||||
Style string `xml:"style,attr"`
|
||||
}
|
||||
|
||||
type BindingOperationIO struct {
|
||||
Text string `xml:",chardata"`
|
||||
Body OperationIOBody `xml:"body"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Port []Port `xml:"port"`
|
||||
}
|
||||
|
||||
type Port struct {
|
||||
Text string `xml:",chardata"`
|
||||
Name string `xml:"name,attr"`
|
||||
Binding string `xml:"binding,attr"`
|
||||
Address Address `xml:"address"`
|
||||
EndpointReference PortEndpointReference `xml:"EndpointReference"`
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
Text string `xml:",chardata"`
|
||||
Location string `xml:"location,attr"`
|
||||
}
|
||||
|
||||
type PortEndpointReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
Address Text `xml:"Address"`
|
||||
Identity Identity `xml:"Identity"`
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
Text string `xml:",chardata"`
|
||||
XMLNS string `xml:"xmlns,attr"`
|
||||
SPN Text `xml:"Spn"`
|
||||
}
|
||||
Generated
Vendored
+230
@@ -0,0 +1,230 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package defs
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// TODO(msal): Someone (and it ain't gonna be me) needs to document these attributes or
|
||||
// at the least put a link to RFC.
|
||||
|
||||
type SAMLDefinitions struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Text string `xml:",chardata"`
|
||||
S string `xml:"s,attr"`
|
||||
A string `xml:"a,attr"`
|
||||
U string `xml:"u,attr"`
|
||||
Header Header `xml:"Header"`
|
||||
Body Body `xml:"Body"`
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
Text string `xml:",chardata"`
|
||||
Action Action `xml:"Action"`
|
||||
Security Security `xml:"Security"`
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"mustUnderstand,attr"`
|
||||
}
|
||||
|
||||
type Security struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"mustUnderstand,attr"`
|
||||
O string `xml:"o,attr"`
|
||||
Timestamp Timestamp `xml:"Timestamp"`
|
||||
}
|
||||
|
||||
type Timestamp struct {
|
||||
Text string `xml:",chardata"`
|
||||
ID string `xml:"Id,attr"`
|
||||
Created Text `xml:"Created"`
|
||||
Expires Text `xml:"Expires"`
|
||||
}
|
||||
|
||||
type Text struct {
|
||||
Text string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequestSecurityTokenResponseCollection RequestSecurityTokenResponseCollection `xml:"RequestSecurityTokenResponseCollection"`
|
||||
}
|
||||
|
||||
type RequestSecurityTokenResponseCollection struct {
|
||||
Text string `xml:",chardata"`
|
||||
Trust string `xml:"trust,attr"`
|
||||
RequestSecurityTokenResponse []RequestSecurityTokenResponse `xml:"RequestSecurityTokenResponse"`
|
||||
}
|
||||
|
||||
type RequestSecurityTokenResponse struct {
|
||||
Text string `xml:",chardata"`
|
||||
Lifetime Lifetime `xml:"Lifetime"`
|
||||
AppliesTo AppliesTo `xml:"AppliesTo"`
|
||||
RequestedSecurityToken RequestedSecurityToken `xml:"RequestedSecurityToken"`
|
||||
RequestedAttachedReference RequestedAttachedReference `xml:"RequestedAttachedReference"`
|
||||
RequestedUnattachedReference RequestedUnattachedReference `xml:"RequestedUnattachedReference"`
|
||||
TokenType Text `xml:"TokenType"`
|
||||
RequestType Text `xml:"RequestType"`
|
||||
KeyType Text `xml:"KeyType"`
|
||||
}
|
||||
|
||||
type Lifetime struct {
|
||||
Text string `xml:",chardata"`
|
||||
Created WSUTimestamp `xml:"Created"`
|
||||
Expires WSUTimestamp `xml:"Expires"`
|
||||
}
|
||||
|
||||
type WSUTimestamp struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wsu string `xml:"wsu,attr"`
|
||||
}
|
||||
|
||||
type AppliesTo struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wsp string `xml:"wsp,attr"`
|
||||
EndpointReference EndpointReference `xml:"EndpointReference"`
|
||||
}
|
||||
|
||||
type EndpointReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wsa string `xml:"wsa,attr"`
|
||||
Address Text `xml:"Address"`
|
||||
}
|
||||
|
||||
type RequestedSecurityToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
AssertionRawXML string `xml:",innerxml"`
|
||||
Assertion Assertion `xml:"Assertion"`
|
||||
}
|
||||
|
||||
type Assertion struct {
|
||||
XMLName xml.Name // Normally its `xml:"Assertion"`, but I think they want to capture the xmlns
|
||||
Text string `xml:",chardata"`
|
||||
MajorVersion string `xml:"MajorVersion,attr"`
|
||||
MinorVersion string `xml:"MinorVersion,attr"`
|
||||
AssertionID string `xml:"AssertionID,attr"`
|
||||
Issuer string `xml:"Issuer,attr"`
|
||||
IssueInstant string `xml:"IssueInstant,attr"`
|
||||
Saml string `xml:"saml,attr"`
|
||||
Conditions Conditions `xml:"Conditions"`
|
||||
AttributeStatement AttributeStatement `xml:"AttributeStatement"`
|
||||
AuthenticationStatement AuthenticationStatement `xml:"AuthenticationStatement"`
|
||||
Signature Signature `xml:"Signature"`
|
||||
}
|
||||
|
||||
type Conditions struct {
|
||||
Text string `xml:",chardata"`
|
||||
NotBefore string `xml:"NotBefore,attr"`
|
||||
NotOnOrAfter string `xml:"NotOnOrAfter,attr"`
|
||||
AudienceRestrictionCondition AudienceRestrictionCondition `xml:"AudienceRestrictionCondition"`
|
||||
}
|
||||
|
||||
type AudienceRestrictionCondition struct {
|
||||
Text string `xml:",chardata"`
|
||||
Audience Text `xml:"Audience"`
|
||||
}
|
||||
|
||||
type AttributeStatement struct {
|
||||
Text string `xml:",chardata"`
|
||||
Subject Subject `xml:"Subject"`
|
||||
Attribute []Attribute `xml:"Attribute"`
|
||||
}
|
||||
|
||||
type Subject struct {
|
||||
Text string `xml:",chardata"`
|
||||
NameIdentifier NameIdentifier `xml:"NameIdentifier"`
|
||||
SubjectConfirmation SubjectConfirmation `xml:"SubjectConfirmation"`
|
||||
}
|
||||
|
||||
type NameIdentifier struct {
|
||||
Text string `xml:",chardata"`
|
||||
Format string `xml:"Format,attr"`
|
||||
}
|
||||
|
||||
type SubjectConfirmation struct {
|
||||
Text string `xml:",chardata"`
|
||||
ConfirmationMethod Text `xml:"ConfirmationMethod"`
|
||||
}
|
||||
|
||||
type Attribute struct {
|
||||
Text string `xml:",chardata"`
|
||||
AttributeName string `xml:"AttributeName,attr"`
|
||||
AttributeNamespace string `xml:"AttributeNamespace,attr"`
|
||||
AttributeValue Text `xml:"AttributeValue"`
|
||||
}
|
||||
|
||||
type AuthenticationStatement struct {
|
||||
Text string `xml:",chardata"`
|
||||
AuthenticationMethod string `xml:"AuthenticationMethod,attr"`
|
||||
AuthenticationInstant string `xml:"AuthenticationInstant,attr"`
|
||||
Subject Subject `xml:"Subject"`
|
||||
}
|
||||
|
||||
type Signature struct {
|
||||
Text string `xml:",chardata"`
|
||||
Ds string `xml:"ds,attr"`
|
||||
SignedInfo SignedInfo `xml:"SignedInfo"`
|
||||
SignatureValue Text `xml:"SignatureValue"`
|
||||
KeyInfo KeyInfo `xml:"KeyInfo"`
|
||||
}
|
||||
|
||||
type SignedInfo struct {
|
||||
Text string `xml:",chardata"`
|
||||
CanonicalizationMethod Method `xml:"CanonicalizationMethod"`
|
||||
SignatureMethod Method `xml:"SignatureMethod"`
|
||||
Reference Reference `xml:"Reference"`
|
||||
}
|
||||
|
||||
type Method struct {
|
||||
Text string `xml:",chardata"`
|
||||
Algorithm string `xml:"Algorithm,attr"`
|
||||
}
|
||||
|
||||
type Reference struct {
|
||||
Text string `xml:",chardata"`
|
||||
URI string `xml:"URI,attr"`
|
||||
Transforms Transforms `xml:"Transforms"`
|
||||
DigestMethod Method `xml:"DigestMethod"`
|
||||
DigestValue Text `xml:"DigestValue"`
|
||||
}
|
||||
|
||||
type Transforms struct {
|
||||
Text string `xml:",chardata"`
|
||||
Transform []Method `xml:"Transform"`
|
||||
}
|
||||
|
||||
type KeyInfo struct {
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
X509Data X509Data `xml:"X509Data"`
|
||||
}
|
||||
|
||||
type X509Data struct {
|
||||
Text string `xml:",chardata"`
|
||||
X509Certificate Text `xml:"X509Certificate"`
|
||||
}
|
||||
|
||||
type RequestedAttachedReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
SecurityTokenReference SecurityTokenReference `xml:"SecurityTokenReference"`
|
||||
}
|
||||
|
||||
type SecurityTokenReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
TokenType string `xml:"TokenType,attr"`
|
||||
O string `xml:"o,attr"`
|
||||
K string `xml:"k,attr"`
|
||||
KeyIdentifier KeyIdentifier `xml:"KeyIdentifier"`
|
||||
}
|
||||
|
||||
type KeyIdentifier struct {
|
||||
Text string `xml:",chardata"`
|
||||
ValueType string `xml:"ValueType,attr"`
|
||||
}
|
||||
|
||||
type RequestedUnattachedReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
SecurityTokenReference SecurityTokenReference `xml:"SecurityTokenReference"`
|
||||
}
|
||||
Generated
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
// Code generated by "stringer -type=Version"; DO NOT EDIT.
|
||||
|
||||
package defs
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[TrustUnknown-0]
|
||||
_ = x[Trust2005-1]
|
||||
_ = x[Trust13-2]
|
||||
}
|
||||
|
||||
const _Version_name = "TrustUnknownTrust2005Trust13"
|
||||
|
||||
var _Version_index = [...]uint8{0, 12, 21, 28}
|
||||
|
||||
func (i Version) String() string {
|
||||
if i < 0 || i >= Version(len(_Version_index)-1) {
|
||||
return "Version(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Version_name[_Version_index[i]:_Version_index[i+1]]
|
||||
}
|
||||
Generated
Vendored
+199
@@ -0,0 +1,199 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package defs
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
uuid "github.com/google/uuid"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=Version
|
||||
|
||||
type Version int
|
||||
|
||||
const (
|
||||
TrustUnknown Version = iota
|
||||
Trust2005
|
||||
Trust13
|
||||
)
|
||||
|
||||
// Endpoint represents a WSTrust endpoint.
|
||||
type Endpoint struct {
|
||||
// Version is the version of the endpoint.
|
||||
Version Version
|
||||
// URL is the URL of the endpoint.
|
||||
URL string
|
||||
}
|
||||
|
||||
type wsTrustTokenRequestEnvelope struct {
|
||||
XMLName xml.Name `xml:"s:Envelope"`
|
||||
Text string `xml:",chardata"`
|
||||
S string `xml:"xmlns:s,attr"`
|
||||
Wsa string `xml:"xmlns:wsa,attr"`
|
||||
Wsu string `xml:"xmlns:wsu,attr"`
|
||||
Header struct {
|
||||
Text string `xml:",chardata"`
|
||||
Action struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"s:mustUnderstand,attr"`
|
||||
} `xml:"wsa:Action"`
|
||||
MessageID struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsa:messageID"`
|
||||
ReplyTo struct {
|
||||
Text string `xml:",chardata"`
|
||||
Address struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsa:Address"`
|
||||
} `xml:"wsa:ReplyTo"`
|
||||
To struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"s:mustUnderstand,attr"`
|
||||
} `xml:"wsa:To"`
|
||||
Security struct {
|
||||
Text string `xml:",chardata"`
|
||||
MustUnderstand string `xml:"s:mustUnderstand,attr"`
|
||||
Wsse string `xml:"xmlns:wsse,attr"`
|
||||
Timestamp struct {
|
||||
Text string `xml:",chardata"`
|
||||
ID string `xml:"wsu:Id,attr"`
|
||||
Created struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsu:Created"`
|
||||
Expires struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsu:Expires"`
|
||||
} `xml:"wsu:Timestamp"`
|
||||
UsernameToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
ID string `xml:"wsu:Id,attr"`
|
||||
Username struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsse:Username"`
|
||||
Password struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsse:Password"`
|
||||
} `xml:"wsse:UsernameToken"`
|
||||
} `xml:"wsse:Security"`
|
||||
} `xml:"s:Header"`
|
||||
Body struct {
|
||||
Text string `xml:",chardata"`
|
||||
RequestSecurityToken struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wst string `xml:"xmlns:wst,attr"`
|
||||
AppliesTo struct {
|
||||
Text string `xml:",chardata"`
|
||||
Wsp string `xml:"xmlns:wsp,attr"`
|
||||
EndpointReference struct {
|
||||
Text string `xml:",chardata"`
|
||||
Address struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wsa:Address"`
|
||||
} `xml:"wsa:EndpointReference"`
|
||||
} `xml:"wsp:AppliesTo"`
|
||||
KeyType struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wst:KeyType"`
|
||||
RequestType struct {
|
||||
Text string `xml:",chardata"`
|
||||
} `xml:"wst:RequestType"`
|
||||
} `xml:"wst:RequestSecurityToken"`
|
||||
} `xml:"s:Body"`
|
||||
}
|
||||
|
||||
func buildTimeString(t time.Time) string {
|
||||
// Golang time formats are weird: https://stackoverflow.com/questions/20234104/how-to-format-current-time-using-a-yyyymmddhhmmss-format
|
||||
return t.Format("2006-01-02T15:04:05.000Z")
|
||||
}
|
||||
|
||||
func (wte *Endpoint) buildTokenRequestMessage(authType authority.AuthorizeType, cloudAudienceURN string, username string, password string) (string, error) {
|
||||
var soapAction string
|
||||
var trustNamespace string
|
||||
var keyType string
|
||||
var requestType string
|
||||
|
||||
createdTime := time.Now().UTC()
|
||||
expiresTime := createdTime.Add(10 * time.Minute)
|
||||
|
||||
switch wte.Version {
|
||||
case Trust2005:
|
||||
soapAction = trust2005Spec
|
||||
trustNamespace = "http://schemas.xmlsoap.org/ws/2005/02/trust"
|
||||
keyType = "http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey"
|
||||
requestType = "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue"
|
||||
case Trust13:
|
||||
soapAction = trust13Spec
|
||||
trustNamespace = "http://docs.oasis-open.org/ws-sx/ws-trust/200512"
|
||||
keyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer"
|
||||
requestType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue"
|
||||
default:
|
||||
return "", fmt.Errorf("buildTokenRequestMessage had Version == %q, which is not recognized", wte.Version)
|
||||
}
|
||||
|
||||
var envelope wsTrustTokenRequestEnvelope
|
||||
|
||||
messageUUID := uuid.New()
|
||||
|
||||
envelope.S = "http://www.w3.org/2003/05/soap-envelope"
|
||||
envelope.Wsa = "http://www.w3.org/2005/08/addressing"
|
||||
envelope.Wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
|
||||
|
||||
envelope.Header.Action.MustUnderstand = "1"
|
||||
envelope.Header.Action.Text = soapAction
|
||||
envelope.Header.MessageID.Text = "urn:uuid:" + messageUUID.String()
|
||||
envelope.Header.ReplyTo.Address.Text = "http://www.w3.org/2005/08/addressing/anonymous"
|
||||
envelope.Header.To.MustUnderstand = "1"
|
||||
envelope.Header.To.Text = wte.URL
|
||||
|
||||
switch authType {
|
||||
case authority.ATUnknown:
|
||||
return "", fmt.Errorf("buildTokenRequestMessage had no authority type(%v)", authType)
|
||||
case authority.ATUsernamePassword:
|
||||
endpointUUID := uuid.New()
|
||||
|
||||
var trustID string
|
||||
if wte.Version == Trust2005 {
|
||||
trustID = "UnPwSecTok2005-" + endpointUUID.String()
|
||||
} else {
|
||||
trustID = "UnPwSecTok13-" + endpointUUID.String()
|
||||
}
|
||||
|
||||
envelope.Header.Security.MustUnderstand = "1"
|
||||
envelope.Header.Security.Wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
|
||||
envelope.Header.Security.Timestamp.ID = "MSATimeStamp"
|
||||
envelope.Header.Security.Timestamp.Created.Text = buildTimeString(createdTime)
|
||||
envelope.Header.Security.Timestamp.Expires.Text = buildTimeString(expiresTime)
|
||||
envelope.Header.Security.UsernameToken.ID = trustID
|
||||
envelope.Header.Security.UsernameToken.Username.Text = username
|
||||
envelope.Header.Security.UsernameToken.Password.Text = password
|
||||
default:
|
||||
// This is just to note that we don't do anything for other cases.
|
||||
// We aren't missing anything I know of.
|
||||
}
|
||||
|
||||
envelope.Body.RequestSecurityToken.Wst = trustNamespace
|
||||
envelope.Body.RequestSecurityToken.AppliesTo.Wsp = "http://schemas.xmlsoap.org/ws/2004/09/policy"
|
||||
envelope.Body.RequestSecurityToken.AppliesTo.EndpointReference.Address.Text = cloudAudienceURN
|
||||
envelope.Body.RequestSecurityToken.KeyType.Text = keyType
|
||||
envelope.Body.RequestSecurityToken.RequestType.Text = requestType
|
||||
|
||||
output, err := xml.Marshal(envelope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
func (wte *Endpoint) BuildTokenRequestMessageWIA(cloudAudienceURN string) (string, error) {
|
||||
return wte.buildTokenRequestMessage(authority.ATWindowsIntegrated, cloudAudienceURN, "", "")
|
||||
}
|
||||
|
||||
func (wte *Endpoint) BuildTokenRequestMessageUsernamePassword(cloudAudienceURN string, username string, password string) (string, error) {
|
||||
return wte.buildTokenRequestMessage(authority.ATUsernamePassword, cloudAudienceURN, username, password)
|
||||
}
|
||||
Generated
Vendored
+159
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package defs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=endpointType
|
||||
|
||||
type endpointType int
|
||||
|
||||
const (
|
||||
etUnknown endpointType = iota
|
||||
etUsernamePassword
|
||||
etWindowsTransport
|
||||
)
|
||||
|
||||
type wsEndpointData struct {
|
||||
Version Version
|
||||
EndpointType endpointType
|
||||
}
|
||||
|
||||
const trust13Spec string = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue"
|
||||
const trust2005Spec string = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue"
|
||||
|
||||
type MexDocument struct {
|
||||
UsernamePasswordEndpoint Endpoint
|
||||
WindowsTransportEndpoint Endpoint
|
||||
policies map[string]endpointType
|
||||
bindings map[string]wsEndpointData
|
||||
}
|
||||
|
||||
func updateEndpoint(cached *Endpoint, found Endpoint) {
|
||||
if cached == nil || cached.Version == TrustUnknown {
|
||||
*cached = found
|
||||
return
|
||||
}
|
||||
if (*cached).Version == Trust2005 && found.Version == Trust13 {
|
||||
*cached = found
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(msal): Someone needs to write tests for everything below.
|
||||
|
||||
// NewFromDef creates a new MexDocument.
|
||||
func NewFromDef(defs Definitions) (MexDocument, error) {
|
||||
policies, err := policies(defs)
|
||||
if err != nil {
|
||||
return MexDocument{}, err
|
||||
}
|
||||
|
||||
bindings, err := bindings(defs, policies)
|
||||
if err != nil {
|
||||
return MexDocument{}, err
|
||||
}
|
||||
|
||||
userPass, windows, err := endpoints(defs, bindings)
|
||||
if err != nil {
|
||||
return MexDocument{}, err
|
||||
}
|
||||
|
||||
return MexDocument{
|
||||
UsernamePasswordEndpoint: userPass,
|
||||
WindowsTransportEndpoint: windows,
|
||||
policies: policies,
|
||||
bindings: bindings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func policies(defs Definitions) (map[string]endpointType, error) {
|
||||
policies := make(map[string]endpointType, len(defs.Policy))
|
||||
|
||||
for _, policy := range defs.Policy {
|
||||
if policy.ExactlyOne.All.NegotiateAuthentication.XMLName.Local != "" {
|
||||
if policy.ExactlyOne.All.TransportBinding.SP != "" && policy.ID != "" {
|
||||
policies["#"+policy.ID] = etWindowsTransport
|
||||
}
|
||||
}
|
||||
|
||||
if policy.ExactlyOne.All.SignedEncryptedSupportingTokens.Policy.UsernameToken.Policy.WSSUsernameToken10.XMLName.Local != "" {
|
||||
if policy.ExactlyOne.All.TransportBinding.SP != "" && policy.ID != "" {
|
||||
policies["#"+policy.ID] = etUsernamePassword
|
||||
}
|
||||
}
|
||||
if policy.ExactlyOne.All.SignedSupportingTokens.Policy.UsernameToken.Policy.WSSUsernameToken10.XMLName.Local != "" {
|
||||
if policy.ExactlyOne.All.TransportBinding.SP != "" && policy.ID != "" {
|
||||
policies["#"+policy.ID] = etUsernamePassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(policies) == 0 {
|
||||
return policies, errors.New("no policies for mex document")
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
func bindings(defs Definitions, policies map[string]endpointType) (map[string]wsEndpointData, error) {
|
||||
bindings := make(map[string]wsEndpointData, len(defs.Binding))
|
||||
|
||||
for _, binding := range defs.Binding {
|
||||
policyName := binding.PolicyReference.URI
|
||||
transport := binding.Binding.Transport
|
||||
|
||||
if transport == "http://schemas.xmlsoap.org/soap/http" {
|
||||
if policy, ok := policies[policyName]; ok {
|
||||
bindingName := binding.Name
|
||||
specVersion := binding.Operation.Operation.SoapAction
|
||||
|
||||
if specVersion == trust13Spec {
|
||||
bindings[bindingName] = wsEndpointData{Trust13, policy}
|
||||
} else if specVersion == trust2005Spec {
|
||||
bindings[bindingName] = wsEndpointData{Trust2005, policy}
|
||||
} else {
|
||||
return nil, errors.New("found unknown spec version in mex document")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bindings, nil
|
||||
}
|
||||
|
||||
func endpoints(defs Definitions, bindings map[string]wsEndpointData) (userPass, windows Endpoint, err error) {
|
||||
for _, port := range defs.Service.Port {
|
||||
bindingName := port.Binding
|
||||
|
||||
index := strings.Index(bindingName, ":")
|
||||
if index != -1 {
|
||||
bindingName = bindingName[index+1:]
|
||||
}
|
||||
|
||||
if binding, ok := bindings[bindingName]; ok {
|
||||
url := strings.TrimSpace(port.EndpointReference.Address.Text)
|
||||
if url == "" {
|
||||
return Endpoint{}, Endpoint{}, fmt.Errorf("MexDocument cannot have blank URL endpoint")
|
||||
}
|
||||
if binding.Version == TrustUnknown {
|
||||
return Endpoint{}, Endpoint{}, fmt.Errorf("endpoint version unknown")
|
||||
}
|
||||
endpoint := Endpoint{Version: binding.Version, URL: url}
|
||||
|
||||
switch binding.EndpointType {
|
||||
case etUsernamePassword:
|
||||
updateEndpoint(&userPass, endpoint)
|
||||
case etWindowsTransport:
|
||||
updateEndpoint(&windows, endpoint)
|
||||
default:
|
||||
return Endpoint{}, Endpoint{}, errors.New("found unknown port type in MEX document")
|
||||
}
|
||||
}
|
||||
}
|
||||
return userPass, windows, nil
|
||||
}
|
||||
Generated
Vendored
+136
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/*
|
||||
Package wstrust provides a client for communicating with a WSTrust (https://en.wikipedia.org/wiki/WS-Trust#:~:text=WS%2DTrust%20is%20a%20WS,in%20a%20secure%20message%20exchange.)
|
||||
for the purposes of extracting metadata from the service. This data can be used to acquire
|
||||
tokens using the accesstokens.Client.GetAccessTokenFromSamlGrant() call.
|
||||
*/
|
||||
package wstrust
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs"
|
||||
)
|
||||
|
||||
type xmlCaller interface {
|
||||
XMLCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, resp interface{}) error
|
||||
SOAPCall(ctx context.Context, endpoint, action string, headers http.Header, qv url.Values, body string, resp interface{}) error
|
||||
}
|
||||
|
||||
type SamlTokenInfo struct {
|
||||
AssertionType string // Should be either constants SAMLV1Grant or SAMLV2Grant.
|
||||
Assertion string
|
||||
}
|
||||
|
||||
// Client represents the REST calls to get tokens from token generator backends.
|
||||
type Client struct {
|
||||
// Comm provides the HTTP transport client.
|
||||
Comm xmlCaller
|
||||
}
|
||||
|
||||
// TODO(msal): This allows me to call Mex without having a real Def file on line 45.
|
||||
// This would fail because policies() would not find a policy. This is easy enough to
|
||||
// fix in test data, but.... Definitions is defined with built in structs. That needs
|
||||
// to be pulled apart and until then I have this hack in.
|
||||
var newFromDef = defs.NewFromDef
|
||||
|
||||
// Mex provides metadata about a wstrust service.
|
||||
func (c Client) Mex(ctx context.Context, federationMetadataURL string) (defs.MexDocument, error) {
|
||||
resp := defs.Definitions{}
|
||||
err := c.Comm.XMLCall(
|
||||
ctx,
|
||||
federationMetadataURL,
|
||||
http.Header{},
|
||||
nil,
|
||||
&resp,
|
||||
)
|
||||
if err != nil {
|
||||
return defs.MexDocument{}, err
|
||||
}
|
||||
|
||||
return newFromDef(resp)
|
||||
}
|
||||
|
||||
const (
|
||||
SoapActionDefault = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue"
|
||||
|
||||
// Note: Commented out because this action is not supported. It was in the original code
|
||||
// but only used in a switch where it errored. Since there was only one value, a default
|
||||
// worked better. However, buildTokenRequestMessage() had 2005 support. I'm not actually
|
||||
// sure what's going on here. It like we have half support. For now this is here just
|
||||
// for documentation purposes in case we are going to add support.
|
||||
//
|
||||
// SoapActionWSTrust2005 = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue"
|
||||
)
|
||||
|
||||
// SAMLTokenInfo provides SAML information that is used to generate a SAML token.
|
||||
func (c Client) SAMLTokenInfo(ctx context.Context, authParameters authority.AuthParams, cloudAudienceURN string, endpoint defs.Endpoint) (SamlTokenInfo, error) {
|
||||
var wsTrustRequestMessage string
|
||||
var err error
|
||||
|
||||
switch authParameters.AuthorizationType {
|
||||
case authority.ATWindowsIntegrated:
|
||||
wsTrustRequestMessage, err = endpoint.BuildTokenRequestMessageWIA(cloudAudienceURN)
|
||||
if err != nil {
|
||||
return SamlTokenInfo{}, err
|
||||
}
|
||||
case authority.ATUsernamePassword:
|
||||
wsTrustRequestMessage, err = endpoint.BuildTokenRequestMessageUsernamePassword(
|
||||
cloudAudienceURN, authParameters.Username, authParameters.Password)
|
||||
if err != nil {
|
||||
return SamlTokenInfo{}, err
|
||||
}
|
||||
default:
|
||||
return SamlTokenInfo{}, fmt.Errorf("unknown auth type %v", authParameters.AuthorizationType)
|
||||
}
|
||||
|
||||
var soapAction string
|
||||
switch endpoint.Version {
|
||||
case defs.Trust13:
|
||||
soapAction = SoapActionDefault
|
||||
case defs.Trust2005:
|
||||
return SamlTokenInfo{}, errors.New("WS Trust 2005 support is not implemented")
|
||||
default:
|
||||
return SamlTokenInfo{}, fmt.Errorf("the SOAP endpoint for a wstrust call had an invalid version: %v", endpoint.Version)
|
||||
}
|
||||
|
||||
resp := defs.SAMLDefinitions{}
|
||||
err = c.Comm.SOAPCall(ctx, endpoint.URL, soapAction, http.Header{}, nil, wsTrustRequestMessage, &resp)
|
||||
if err != nil {
|
||||
return SamlTokenInfo{}, err
|
||||
}
|
||||
|
||||
return c.samlAssertion(resp)
|
||||
}
|
||||
|
||||
const (
|
||||
samlv1Assertion = "urn:oasis:names:tc:SAML:1.0:assertion"
|
||||
samlv2Assertion = "urn:oasis:names:tc:SAML:2.0:assertion"
|
||||
)
|
||||
|
||||
func (c Client) samlAssertion(def defs.SAMLDefinitions) (SamlTokenInfo, error) {
|
||||
for _, tokenResponse := range def.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse {
|
||||
token := tokenResponse.RequestedSecurityToken
|
||||
if token.Assertion.XMLName.Local != "" {
|
||||
assertion := token.AssertionRawXML
|
||||
|
||||
samlVersion := token.Assertion.Saml
|
||||
switch samlVersion {
|
||||
case samlv1Assertion:
|
||||
return SamlTokenInfo{AssertionType: grant.SAMLV1, Assertion: assertion}, nil
|
||||
case samlv2Assertion:
|
||||
return SamlTokenInfo{AssertionType: grant.SAMLV2, Assertion: assertion}, nil
|
||||
}
|
||||
return SamlTokenInfo{}, fmt.Errorf("couldn't parse SAML assertion, version unknown: %q", samlVersion)
|
||||
}
|
||||
}
|
||||
return SamlTokenInfo{}, errors.New("unknown WS-Trust version")
|
||||
}
|
||||
Generated
Vendored
+148
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// TODO(msal): Write some tests. The original code this came from didn't have tests and I'm too
|
||||
// tired at this point to do it. It, like many other *Manager code I found was broken because
|
||||
// they didn't have mutex protection.
|
||||
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops"
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
|
||||
)
|
||||
|
||||
type cacheEntry struct {
|
||||
Endpoints authority.Endpoints
|
||||
ValidForDomainsInList map[string]bool
|
||||
}
|
||||
|
||||
func createcacheEntry(endpoints authority.Endpoints) cacheEntry {
|
||||
return cacheEntry{endpoints, map[string]bool{}}
|
||||
}
|
||||
|
||||
// AuthorityEndpoint retrieves endpoints from an authority for auth and token acquisition.
|
||||
type authorityEndpoint struct {
|
||||
rest *ops.REST
|
||||
|
||||
mu sync.Mutex
|
||||
cache map[string]cacheEntry
|
||||
}
|
||||
|
||||
// newAuthorityEndpoint is the constructor for AuthorityEndpoint.
|
||||
func newAuthorityEndpoint(rest *ops.REST) *authorityEndpoint {
|
||||
m := &authorityEndpoint{rest: rest, cache: map[string]cacheEntry{}}
|
||||
return m
|
||||
}
|
||||
|
||||
// ResolveEndpoints gets the authorization and token endpoints and creates an AuthorityEndpoints instance
|
||||
func (m *authorityEndpoint) ResolveEndpoints(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, error) {
|
||||
|
||||
if endpoints, found := m.cachedEndpoints(authorityInfo, userPrincipalName); found {
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
endpoint, err := m.openIDConfigurationEndpoint(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
return authority.Endpoints{}, err
|
||||
}
|
||||
|
||||
resp, err := m.rest.Authority().GetTenantDiscoveryResponse(ctx, endpoint)
|
||||
if err != nil {
|
||||
return authority.Endpoints{}, err
|
||||
}
|
||||
if err := resp.Validate(); err != nil {
|
||||
return authority.Endpoints{}, fmt.Errorf("ResolveEndpoints(): %w", err)
|
||||
}
|
||||
|
||||
tenant := authorityInfo.Tenant
|
||||
|
||||
endpoints := authority.NewEndpoints(
|
||||
strings.Replace(resp.AuthorizationEndpoint, "{tenant}", tenant, -1),
|
||||
strings.Replace(resp.TokenEndpoint, "{tenant}", tenant, -1),
|
||||
strings.Replace(resp.Issuer, "{tenant}", tenant, -1),
|
||||
authorityInfo.Host)
|
||||
|
||||
m.addCachedEndpoints(authorityInfo, userPrincipalName, endpoints)
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// cachedEndpoints returns a the cached endpoints if they exists. If not, we return false.
|
||||
func (m *authorityEndpoint) cachedEndpoints(authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if cacheEntry, ok := m.cache[authorityInfo.CanonicalAuthorityURI]; ok {
|
||||
if authorityInfo.AuthorityType == authority.ADFS {
|
||||
domain, err := adfsDomainFromUpn(userPrincipalName)
|
||||
if err == nil {
|
||||
if _, ok := cacheEntry.ValidForDomainsInList[domain]; ok {
|
||||
return cacheEntry.Endpoints, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return cacheEntry.Endpoints, true
|
||||
}
|
||||
return authority.Endpoints{}, false
|
||||
}
|
||||
|
||||
func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, userPrincipalName string, endpoints authority.Endpoints) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
updatedCacheEntry := createcacheEntry(endpoints)
|
||||
|
||||
if authorityInfo.AuthorityType == authority.ADFS {
|
||||
// Since we're here, we've made a call to the backend. We want to ensure we're caching
|
||||
// the latest values from the server.
|
||||
if cacheEntry, ok := m.cache[authorityInfo.CanonicalAuthorityURI]; ok {
|
||||
for k := range cacheEntry.ValidForDomainsInList {
|
||||
updatedCacheEntry.ValidForDomainsInList[k] = true
|
||||
}
|
||||
}
|
||||
domain, err := adfsDomainFromUpn(userPrincipalName)
|
||||
if err == nil {
|
||||
updatedCacheEntry.ValidForDomainsInList[domain] = true
|
||||
}
|
||||
}
|
||||
|
||||
m.cache[authorityInfo.CanonicalAuthorityURI] = updatedCacheEntry
|
||||
}
|
||||
|
||||
func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, authorityInfo authority.Info) (string, error) {
|
||||
if authorityInfo.AuthorityType == authority.ADFS {
|
||||
return fmt.Sprintf("https://%s/adfs/.well-known/openid-configuration", authorityInfo.Host), nil
|
||||
} else if authorityInfo.AuthorityType == authority.DSTS {
|
||||
return fmt.Sprintf("https://%s/dstsv2/%s/v2.0/.well-known/openid-configuration", authorityInfo.Host, authority.DSTSTenant), nil
|
||||
|
||||
} else if authorityInfo.ValidateAuthority && !authority.TrustedHost(authorityInfo.Host) {
|
||||
resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.TenantDiscoveryEndpoint, nil
|
||||
} else if authorityInfo.Region != "" {
|
||||
resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.TenantDiscoveryEndpoint, nil
|
||||
}
|
||||
|
||||
return authorityInfo.CanonicalAuthorityURI + "v2.0/.well-known/openid-configuration", nil
|
||||
}
|
||||
|
||||
func adfsDomainFromUpn(userPrincipalName string) (string, error) {
|
||||
parts := strings.Split(userPrincipalName, "@")
|
||||
if len(parts) < 2 {
|
||||
return "", errors.New("no @ present in user principal name")
|
||||
}
|
||||
return parts[1], nil
|
||||
}
|
||||
Generated
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package options
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CallOption implements an optional argument to a method call. See
|
||||
// https://blog.devgenius.io/go-call-option-that-can-be-used-with-multiple-methods-6c81734f3dbe
|
||||
// for an explanation of the usage pattern.
|
||||
type CallOption interface {
|
||||
Do(any) error
|
||||
callOption()
|
||||
}
|
||||
|
||||
// ApplyOptions applies all the callOptions to options. options must be a pointer to a struct and
|
||||
// callOptions must be a list of objects that implement CallOption.
|
||||
func ApplyOptions[O, C any](options O, callOptions []C) error {
|
||||
for _, o := range callOptions {
|
||||
if t, ok := any(o).(CallOption); !ok {
|
||||
return fmt.Errorf("unexpected option type %T", o)
|
||||
} else if err := t.Do(options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewCallOption returns a new CallOption whose Do() method calls function "f".
|
||||
func NewCallOption(f func(any) error) CallOption {
|
||||
if f == nil {
|
||||
// This isn't a practical concern because only an MSAL maintainer can get
|
||||
// us here, by implementing a do-nothing option. But if someone does that,
|
||||
// the below ensures the method invoked with the option returns an error.
|
||||
return callOption(func(any) error {
|
||||
return errors.New("invalid option: missing implementation")
|
||||
})
|
||||
}
|
||||
return callOption(f)
|
||||
}
|
||||
|
||||
// callOption is an adapter for a function to a CallOption
|
||||
type callOption func(any) error
|
||||
|
||||
func (c callOption) Do(a any) error {
|
||||
return c(a)
|
||||
}
|
||||
|
||||
func (callOption) callOption() {}
|
||||
Generated
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package shared
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// CacheKeySeparator is used in creating the keys of the cache.
|
||||
CacheKeySeparator = "-"
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
HomeAccountID string `json:"home_account_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
LocalAccountID string `json:"local_account_id,omitempty"`
|
||||
AuthorityType string `json:"authority_type,omitempty"`
|
||||
PreferredUsername string `json:"username,omitempty"`
|
||||
GivenName string `json:"given_name,omitempty"`
|
||||
FamilyName string `json:"family_name,omitempty"`
|
||||
MiddleName string `json:"middle_name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
AlternativeID string `json:"alternative_account_id,omitempty"`
|
||||
RawClientInfo string `json:"client_info,omitempty"`
|
||||
UserAssertionHash string `json:"user_assertion_hash,omitempty"`
|
||||
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// NewAccount creates an account.
|
||||
func NewAccount(homeAccountID, env, realm, localAccountID, authorityType, username string) Account {
|
||||
return Account{
|
||||
HomeAccountID: homeAccountID,
|
||||
Environment: env,
|
||||
Realm: realm,
|
||||
LocalAccountID: localAccountID,
|
||||
AuthorityType: authorityType,
|
||||
PreferredUsername: username,
|
||||
}
|
||||
}
|
||||
|
||||
// Key creates the key for storing accounts in the cache.
|
||||
func (acc Account) Key() string {
|
||||
key := strings.Join([]string{acc.HomeAccountID, acc.Environment, acc.Realm}, CacheKeySeparator)
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
// IsZero checks the zero value of account.
|
||||
func (acc Account) IsZero() bool {
|
||||
v := reflect.ValueOf(acc)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if !field.IsZero() {
|
||||
switch field.Kind() {
|
||||
case reflect.Map, reflect.Slice:
|
||||
if field.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DefaultClient is our default shared HTTP client.
|
||||
var DefaultClient = &http.Client{}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Package version keeps the version number of the client package.
|
||||
package version
|
||||
|
||||
// Version is the version of this client package that is communicated to the server.
|
||||
const Version = "1.2.0"
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
language: go
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
go:
|
||||
- go1.13.x
|
||||
- go1.x
|
||||
|
||||
services:
|
||||
- xvfb
|
||||
|
||||
before_install:
|
||||
- export DISPLAY=:99.0
|
||||
|
||||
script:
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi
|
||||
- go test -v .
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi
|
||||
- go test -v .
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2013 Ato Araki. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of @atotto. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
[](https://travis-ci.org/atotto/clipboard)
|
||||
|
||||
[](http://godoc.org/github.com/atotto/clipboard)
|
||||
|
||||
# Clipboard for Go
|
||||
|
||||
Provide copying and pasting to the Clipboard for Go.
|
||||
|
||||
Build:
|
||||
|
||||
$ go get github.com/atotto/clipboard
|
||||
|
||||
Platforms:
|
||||
|
||||
* OSX
|
||||
* Windows 7 (probably work on other Windows)
|
||||
* Linux, Unix (requires 'xclip' or 'xsel' command to be installed)
|
||||
|
||||
|
||||
Document:
|
||||
|
||||
* http://godoc.org/github.com/atotto/clipboard
|
||||
|
||||
Notes:
|
||||
|
||||
* Text string only
|
||||
* UTF-8 text encoding only (no conversion)
|
||||
|
||||
TODO:
|
||||
|
||||
* Clipboard watcher(?)
|
||||
|
||||
## Commands:
|
||||
|
||||
paste shell command:
|
||||
|
||||
$ go get github.com/atotto/clipboard/cmd/gopaste
|
||||
$ # example:
|
||||
$ gopaste > document.txt
|
||||
|
||||
copy shell command:
|
||||
|
||||
$ go get github.com/atotto/clipboard/cmd/gocopy
|
||||
$ # example:
|
||||
$ cat document.txt | gocopy
|
||||
|
||||
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package clipboard read/write on clipboard
|
||||
package clipboard
|
||||
|
||||
// ReadAll read string from clipboard
|
||||
func ReadAll() (string, error) {
|
||||
return readAll()
|
||||
}
|
||||
|
||||
// WriteAll write string to clipboard
|
||||
func WriteAll(text string) error {
|
||||
return writeAll(text)
|
||||
}
|
||||
|
||||
// Unsupported might be set true during clipboard init, to help callers decide
|
||||
// whether or not to offer clipboard options.
|
||||
var Unsupported bool
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var (
|
||||
pasteCmdArgs = "pbpaste"
|
||||
copyCmdArgs = "pbcopy"
|
||||
)
|
||||
|
||||
func getPasteCommand() *exec.Cmd {
|
||||
return exec.Command(pasteCmdArgs)
|
||||
}
|
||||
|
||||
func getCopyCommand() *exec.Cmd {
|
||||
return exec.Command(copyCmdArgs)
|
||||
}
|
||||
|
||||
func readAll() (string, error) {
|
||||
pasteCmd := getPasteCommand()
|
||||
out, err := pasteCmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func writeAll(text string) error {
|
||||
copyCmd := getCopyCommand()
|
||||
in, err := copyCmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := copyCmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := in.Write([]byte(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := in.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyCmd.Wait()
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build plan9
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"os"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func readAll() (string, error) {
|
||||
f, err := os.Open("/dev/snarf")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
str, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(str), nil
|
||||
}
|
||||
|
||||
func writeAll(text string) error {
|
||||
f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write([]byte(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build freebsd linux netbsd openbsd solaris dragonfly
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
xsel = "xsel"
|
||||
xclip = "xclip"
|
||||
powershellExe = "powershell.exe"
|
||||
clipExe = "clip.exe"
|
||||
wlcopy = "wl-copy"
|
||||
wlpaste = "wl-paste"
|
||||
termuxClipboardGet = "termux-clipboard-get"
|
||||
termuxClipboardSet = "termux-clipboard-set"
|
||||
)
|
||||
|
||||
var (
|
||||
Primary bool
|
||||
trimDos bool
|
||||
|
||||
pasteCmdArgs []string
|
||||
copyCmdArgs []string
|
||||
|
||||
xselPasteArgs = []string{xsel, "--output", "--clipboard"}
|
||||
xselCopyArgs = []string{xsel, "--input", "--clipboard"}
|
||||
|
||||
xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
|
||||
xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"}
|
||||
|
||||
powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"}
|
||||
clipExeCopyArgs = []string{clipExe}
|
||||
|
||||
wlpasteArgs = []string{wlpaste, "--no-newline"}
|
||||
wlcopyArgs = []string{wlcopy}
|
||||
|
||||
termuxPasteArgs = []string{termuxClipboardGet}
|
||||
termuxCopyArgs = []string{termuxClipboardSet}
|
||||
|
||||
missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.")
|
||||
)
|
||||
|
||||
func init() {
|
||||
if os.Getenv("WAYLAND_DISPLAY") != "" {
|
||||
pasteCmdArgs = wlpasteArgs
|
||||
copyCmdArgs = wlcopyArgs
|
||||
|
||||
if _, err := exec.LookPath(wlcopy); err == nil {
|
||||
if _, err := exec.LookPath(wlpaste); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pasteCmdArgs = xclipPasteArgs
|
||||
copyCmdArgs = xclipCopyArgs
|
||||
|
||||
if _, err := exec.LookPath(xclip); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pasteCmdArgs = xselPasteArgs
|
||||
copyCmdArgs = xselCopyArgs
|
||||
|
||||
if _, err := exec.LookPath(xsel); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pasteCmdArgs = termuxPasteArgs
|
||||
copyCmdArgs = termuxCopyArgs
|
||||
|
||||
if _, err := exec.LookPath(termuxClipboardSet); err == nil {
|
||||
if _, err := exec.LookPath(termuxClipboardGet); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pasteCmdArgs = powershellExePasteArgs
|
||||
copyCmdArgs = clipExeCopyArgs
|
||||
trimDos = true
|
||||
|
||||
if _, err := exec.LookPath(clipExe); err == nil {
|
||||
if _, err := exec.LookPath(powershellExe); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Unsupported = true
|
||||
}
|
||||
|
||||
func getPasteCommand() *exec.Cmd {
|
||||
if Primary {
|
||||
pasteCmdArgs = pasteCmdArgs[:1]
|
||||
}
|
||||
return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...)
|
||||
}
|
||||
|
||||
func getCopyCommand() *exec.Cmd {
|
||||
if Primary {
|
||||
copyCmdArgs = copyCmdArgs[:1]
|
||||
}
|
||||
return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...)
|
||||
}
|
||||
|
||||
func readAll() (string, error) {
|
||||
if Unsupported {
|
||||
return "", missingCommands
|
||||
}
|
||||
pasteCmd := getPasteCommand()
|
||||
out, err := pasteCmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result := string(out)
|
||||
if trimDos && len(result) > 1 {
|
||||
result = result[:len(result)-2]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func writeAll(text string) error {
|
||||
if Unsupported {
|
||||
return missingCommands
|
||||
}
|
||||
copyCmd := getCopyCommand()
|
||||
in, err := copyCmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := copyCmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := in.Write([]byte(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := in.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyCmd.Wait()
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
cfUnicodetext = 13
|
||||
gmemMoveable = 0x0002
|
||||
)
|
||||
|
||||
var (
|
||||
user32 = syscall.MustLoadDLL("user32")
|
||||
isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable")
|
||||
openClipboard = user32.MustFindProc("OpenClipboard")
|
||||
closeClipboard = user32.MustFindProc("CloseClipboard")
|
||||
emptyClipboard = user32.MustFindProc("EmptyClipboard")
|
||||
getClipboardData = user32.MustFindProc("GetClipboardData")
|
||||
setClipboardData = user32.MustFindProc("SetClipboardData")
|
||||
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
globalAlloc = kernel32.NewProc("GlobalAlloc")
|
||||
globalFree = kernel32.NewProc("GlobalFree")
|
||||
globalLock = kernel32.NewProc("GlobalLock")
|
||||
globalUnlock = kernel32.NewProc("GlobalUnlock")
|
||||
lstrcpy = kernel32.NewProc("lstrcpyW")
|
||||
)
|
||||
|
||||
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
|
||||
func waitOpenClipboard() error {
|
||||
started := time.Now()
|
||||
limit := started.Add(time.Second)
|
||||
var r uintptr
|
||||
var err error
|
||||
for time.Now().Before(limit) {
|
||||
r, _, err = openClipboard.Call(0)
|
||||
if r != 0 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func readAll() (string, error) {
|
||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
|
||||
return "", err
|
||||
}
|
||||
err := waitOpenClipboard()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h, _, err := getClipboardData.Call(cfUnicodetext)
|
||||
if h == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
l, _, err := globalLock.Call(h)
|
||||
if l == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
|
||||
|
||||
r, _, err := globalUnlock.Call(h)
|
||||
if r == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
closed, _, err := closeClipboard.Call()
|
||||
if closed == 0 {
|
||||
return "", err
|
||||
}
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func writeAll(text string) error {
|
||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
err := waitOpenClipboard()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err := emptyClipboard.Call(0)
|
||||
if r == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
data := syscall.StringToUTF16(text)
|
||||
|
||||
// "If the hMem parameter identifies a memory object, the object must have
|
||||
// been allocated using the function with the GMEM_MOVEABLE flag."
|
||||
h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
|
||||
if h == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if h != 0 {
|
||||
globalFree.Call(h)
|
||||
}
|
||||
}()
|
||||
|
||||
l, _, err := globalLock.Call(h)
|
||||
if l == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
|
||||
if r == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err = globalUnlock.Call(h)
|
||||
if r == 0 {
|
||||
if err.(syscall.Errno) != 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r, _, err = setClipboardData.Call(cfUnicodetext, h)
|
||||
if r == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
h = 0 // suppress deferred cleanup
|
||||
closed, _, err := closeClipboard.Call()
|
||||
if closed == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Ayman Bagabas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
|
||||
# go-osc52
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/aymanbagabas/go-osc52/releases"><img src="https://img.shields.io/github/release/aymanbagabas/go-osc52.svg" alt="Latest Release"></a>
|
||||
<a href="https://pkg.go.dev/github.com/aymanbagabas/go-osc52/v2?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="GoDoc"></a>
|
||||
</p>
|
||||
|
||||
A Go library to work with the [ANSI OSC52](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands) terminal sequence.
|
||||
|
||||
## Usage
|
||||
|
||||
You can use this small library to construct an ANSI OSC52 sequence suitable for
|
||||
your terminal.
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
|
||||
"github.com/aymanbagabas/go-osc52/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := "Hello World!"
|
||||
|
||||
// Copy `s` to system clipboard
|
||||
osc52.New(s).WriteTo(os.Stderr)
|
||||
|
||||
// Copy `s` to primary clipboard (X11)
|
||||
osc52.New(s).Primary().WriteTo(os.Stderr)
|
||||
|
||||
// Query the clipboard
|
||||
osc52.Query().WriteTo(os.Stderr)
|
||||
|
||||
// Clear system clipboard
|
||||
osc52.Clear().WriteTo(os.Stderr)
|
||||
|
||||
// Use the fmt.Stringer interface to copy `s` to system clipboard
|
||||
fmt.Fprint(os.Stderr, osc52.New(s))
|
||||
|
||||
// Or to primary clipboard
|
||||
fmt.Fprint(os.Stderr, osc52.New(s).Primary())
|
||||
}
|
||||
```
|
||||
|
||||
## SSH Example
|
||||
|
||||
You can use this over SSH using [gliderlabs/ssh](https://github.com/gliderlabs/ssh) for instance:
|
||||
|
||||
```go
|
||||
var sshSession ssh.Session
|
||||
seq := osc52.New("Hello awesome!")
|
||||
// Check if term is screen or tmux
|
||||
pty, _, _ := s.Pty()
|
||||
if pty.Term == "screen" {
|
||||
seq = seq.Screen()
|
||||
} else if isTmux {
|
||||
seq = seq.Tmux()
|
||||
}
|
||||
seq.WriteTo(sshSession.Stderr())
|
||||
```
|
||||
|
||||
## Tmux
|
||||
|
||||
Make sure you have `set-clipboard on` in your config, otherwise, tmux won't
|
||||
allow your application to access the clipboard [^1].
|
||||
|
||||
Using the tmux option, `osc52.TmuxMode` or `osc52.New(...).Tmux()`, wraps the
|
||||
OSC52 sequence in a special tmux DCS sequence and pass it to the outer
|
||||
terminal. This requires `allow-passthrough on` in your config.
|
||||
`allow-passthrough` is no longer enabled by default
|
||||
[since tmux 3.3a](https://github.com/tmux/tmux/issues/3218#issuecomment-1153089282) [^2].
|
||||
|
||||
[^1]: See [tmux clipboard](https://github.com/tmux/tmux/wiki/Clipboard)
|
||||
[^2]: [What is allow-passthrough](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it)
|
||||
|
||||
## Credits
|
||||
|
||||
* [vim-oscyank](https://github.com/ojroques/vim-oscyank) this is heavily inspired by vim-oscyank.
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
// OSC52 is a terminal escape sequence that allows copying text to the clipboard.
|
||||
//
|
||||
// The sequence consists of the following:
|
||||
//
|
||||
// OSC 52 ; Pc ; Pd BEL
|
||||
//
|
||||
// Pc is the clipboard choice:
|
||||
//
|
||||
// c: clipboard
|
||||
// p: primary
|
||||
// q: secondary (not supported)
|
||||
// s: select (not supported)
|
||||
// 0-7: cut-buffers (not supported)
|
||||
//
|
||||
// Pd is the data to copy to the clipboard. This string should be encoded in
|
||||
// base64 (RFC-4648).
|
||||
//
|
||||
// If Pd is "?", the terminal replies to the host with the current contents of
|
||||
// the clipboard.
|
||||
//
|
||||
// If Pd is neither a base64 string nor "?", the terminal clears the clipboard.
|
||||
//
|
||||
// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
|
||||
// where Ps = 52 => Manipulate Selection Data.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// // copy "hello world" to the system clipboard
|
||||
// fmt.Fprint(os.Stderr, osc52.New("hello world"))
|
||||
//
|
||||
// // copy "hello world" to the primary Clipboard
|
||||
// fmt.Fprint(os.Stderr, osc52.New("hello world").Primary())
|
||||
//
|
||||
// // limit the size of the string to copy 10 bytes
|
||||
// fmt.Fprint(os.Stderr, osc52.New("0123456789").Limit(10))
|
||||
//
|
||||
// // escape the OSC52 sequence for screen using DCS sequences
|
||||
// fmt.Fprint(os.Stderr, osc52.New("hello world").Screen())
|
||||
//
|
||||
// // escape the OSC52 sequence for Tmux
|
||||
// fmt.Fprint(os.Stderr, osc52.New("hello world").Tmux())
|
||||
//
|
||||
// // query the system Clipboard
|
||||
// fmt.Fprint(os.Stderr, osc52.Query())
|
||||
//
|
||||
// // query the primary clipboard
|
||||
// fmt.Fprint(os.Stderr, osc52.Query().Primary())
|
||||
//
|
||||
// // clear the system Clipboard
|
||||
// fmt.Fprint(os.Stderr, osc52.Clear())
|
||||
//
|
||||
// // clear the primary Clipboard
|
||||
// fmt.Fprint(os.Stderr, osc52.Clear().Primary())
|
||||
package osc52
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Clipboard is the clipboard buffer to use.
|
||||
type Clipboard rune
|
||||
|
||||
const (
|
||||
// SystemClipboard is the system clipboard buffer.
|
||||
SystemClipboard Clipboard = 'c'
|
||||
// PrimaryClipboard is the primary clipboard buffer (X11).
|
||||
PrimaryClipboard = 'p'
|
||||
)
|
||||
|
||||
// Mode is the mode to use for the OSC52 sequence.
|
||||
type Mode uint
|
||||
|
||||
const (
|
||||
// DefaultMode is the default OSC52 sequence mode.
|
||||
DefaultMode Mode = iota
|
||||
// ScreenMode escapes the OSC52 sequence for screen using DCS sequences.
|
||||
ScreenMode
|
||||
// TmuxMode escapes the OSC52 sequence for tmux. Not needed if tmux
|
||||
// clipboard is set to `set-clipboard on`
|
||||
TmuxMode
|
||||
)
|
||||
|
||||
// Operation is the OSC52 operation.
|
||||
type Operation uint
|
||||
|
||||
const (
|
||||
// SetOperation is the copy operation.
|
||||
SetOperation Operation = iota
|
||||
// QueryOperation is the query operation.
|
||||
QueryOperation
|
||||
// ClearOperation is the clear operation.
|
||||
ClearOperation
|
||||
)
|
||||
|
||||
// Sequence is the OSC52 sequence.
|
||||
type Sequence struct {
|
||||
str string
|
||||
limit int
|
||||
op Operation
|
||||
mode Mode
|
||||
clipboard Clipboard
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = Sequence{}
|
||||
|
||||
var _ io.WriterTo = Sequence{}
|
||||
|
||||
// String returns the OSC52 sequence.
|
||||
func (s Sequence) String() string {
|
||||
var seq strings.Builder
|
||||
// mode escape sequences start
|
||||
seq.WriteString(s.seqStart())
|
||||
// actual OSC52 sequence start
|
||||
seq.WriteString(fmt.Sprintf("\x1b]52;%c;", s.clipboard))
|
||||
switch s.op {
|
||||
case SetOperation:
|
||||
str := s.str
|
||||
if s.limit > 0 && len(str) > s.limit {
|
||||
return ""
|
||||
}
|
||||
b64 := base64.StdEncoding.EncodeToString([]byte(str))
|
||||
switch s.mode {
|
||||
case ScreenMode:
|
||||
// Screen doesn't support OSC52 but will pass the contents of a DCS
|
||||
// sequence to the outer terminal unchanged.
|
||||
//
|
||||
// Here, we split the encoded string into 76 bytes chunks and then
|
||||
// join the chunks with <end-dsc><start-dsc> sequences. Finally,
|
||||
// wrap the whole thing in
|
||||
// <start-dsc><start-osc52><joined-chunks><end-osc52><end-dsc>.
|
||||
// s := strings.SplitN(b64, "", 76)
|
||||
s := make([]string, 0, len(b64)/76+1)
|
||||
for i := 0; i < len(b64); i += 76 {
|
||||
end := i + 76
|
||||
if end > len(b64) {
|
||||
end = len(b64)
|
||||
}
|
||||
s = append(s, b64[i:end])
|
||||
}
|
||||
seq.WriteString(strings.Join(s, "\x1b\\\x1bP"))
|
||||
default:
|
||||
seq.WriteString(b64)
|
||||
}
|
||||
case QueryOperation:
|
||||
// OSC52 queries the clipboard using "?"
|
||||
seq.WriteString("?")
|
||||
case ClearOperation:
|
||||
// OSC52 clears the clipboard if the data is neither a base64 string nor "?"
|
||||
// we're using "!" as a default
|
||||
seq.WriteString("!")
|
||||
}
|
||||
// actual OSC52 sequence end
|
||||
seq.WriteString("\x07")
|
||||
// mode escape end
|
||||
seq.WriteString(s.seqEnd())
|
||||
return seq.String()
|
||||
}
|
||||
|
||||
// WriteTo writes the OSC52 sequence to the writer.
|
||||
func (s Sequence) WriteTo(out io.Writer) (int64, error) {
|
||||
n, err := out.Write([]byte(s.String()))
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// Mode sets the mode for the OSC52 sequence.
|
||||
func (s Sequence) Mode(m Mode) Sequence {
|
||||
s.mode = m
|
||||
return s
|
||||
}
|
||||
|
||||
// Tmux sets the mode to TmuxMode.
|
||||
// Used to escape the OSC52 sequence for `tmux`.
|
||||
//
|
||||
// Note: this is not needed if tmux clipboard is set to `set-clipboard on`. If
|
||||
// TmuxMode is used, tmux must have `allow-passthrough on` set.
|
||||
//
|
||||
// This is a syntactic sugar for s.Mode(TmuxMode).
|
||||
func (s Sequence) Tmux() Sequence {
|
||||
return s.Mode(TmuxMode)
|
||||
}
|
||||
|
||||
// Screen sets the mode to ScreenMode.
|
||||
// Used to escape the OSC52 sequence for `screen`.
|
||||
//
|
||||
// This is a syntactic sugar for s.Mode(ScreenMode).
|
||||
func (s Sequence) Screen() Sequence {
|
||||
return s.Mode(ScreenMode)
|
||||
}
|
||||
|
||||
// Clipboard sets the clipboard buffer for the OSC52 sequence.
|
||||
func (s Sequence) Clipboard(c Clipboard) Sequence {
|
||||
s.clipboard = c
|
||||
return s
|
||||
}
|
||||
|
||||
// Primary sets the clipboard buffer to PrimaryClipboard.
|
||||
// This is the X11 primary clipboard.
|
||||
//
|
||||
// This is a syntactic sugar for s.Clipboard(PrimaryClipboard).
|
||||
func (s Sequence) Primary() Sequence {
|
||||
return s.Clipboard(PrimaryClipboard)
|
||||
}
|
||||
|
||||
// Limit sets the limit for the OSC52 sequence.
|
||||
// The default limit is 0 (no limit).
|
||||
//
|
||||
// Strings longer than the limit get ignored. Settting the limit to 0 or a
|
||||
// negative value disables the limit. Each terminal defines its own escapse
|
||||
// sequence limit.
|
||||
func (s Sequence) Limit(l int) Sequence {
|
||||
if l < 0 {
|
||||
s.limit = 0
|
||||
} else {
|
||||
s.limit = l
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Operation sets the operation for the OSC52 sequence.
|
||||
// The default operation is SetOperation.
|
||||
func (s Sequence) Operation(o Operation) Sequence {
|
||||
s.op = o
|
||||
return s
|
||||
}
|
||||
|
||||
// Clear sets the operation to ClearOperation.
|
||||
// This clears the clipboard.
|
||||
//
|
||||
// This is a syntactic sugar for s.Operation(ClearOperation).
|
||||
func (s Sequence) Clear() Sequence {
|
||||
return s.Operation(ClearOperation)
|
||||
}
|
||||
|
||||
// Query sets the operation to QueryOperation.
|
||||
// This queries the clipboard contents.
|
||||
//
|
||||
// This is a syntactic sugar for s.Operation(QueryOperation).
|
||||
func (s Sequence) Query() Sequence {
|
||||
return s.Operation(QueryOperation)
|
||||
}
|
||||
|
||||
// SetString sets the string for the OSC52 sequence. Strings are joined with a
|
||||
// space character.
|
||||
func (s Sequence) SetString(strs ...string) Sequence {
|
||||
s.str = strings.Join(strs, " ")
|
||||
return s
|
||||
}
|
||||
|
||||
// New creates a new OSC52 sequence with the given string(s). Strings are
|
||||
// joined with a space character.
|
||||
func New(strs ...string) Sequence {
|
||||
s := Sequence{
|
||||
str: strings.Join(strs, " "),
|
||||
limit: 0,
|
||||
mode: DefaultMode,
|
||||
clipboard: SystemClipboard,
|
||||
op: SetOperation,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Query creates a new OSC52 sequence with the QueryOperation.
|
||||
// This returns a new OSC52 sequence to query the clipboard contents.
|
||||
//
|
||||
// This is a syntactic sugar for New().Query().
|
||||
func Query() Sequence {
|
||||
return New().Query()
|
||||
}
|
||||
|
||||
// Clear creates a new OSC52 sequence with the ClearOperation.
|
||||
// This returns a new OSC52 sequence to clear the clipboard.
|
||||
//
|
||||
// This is a syntactic sugar for New().Clear().
|
||||
func Clear() Sequence {
|
||||
return New().Clear()
|
||||
}
|
||||
|
||||
func (s Sequence) seqStart() string {
|
||||
switch s.mode {
|
||||
case TmuxMode:
|
||||
// Write the start of a tmux escape sequence.
|
||||
return "\x1bPtmux;\x1b"
|
||||
case ScreenMode:
|
||||
// Write the start of a DCS sequence.
|
||||
return "\x1bP"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (s Sequence) seqEnd() string {
|
||||
switch s.mode {
|
||||
case TmuxMode:
|
||||
// Terminate the tmux escape sequence.
|
||||
return "\x1b\\"
|
||||
case ScreenMode:
|
||||
// Write the end of a DCS sequence.
|
||||
return "\x1b\x5c"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
.vscode/
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Florian Sundermann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
[](https://gitter.im/golang-barcode/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
## Introduction ##
|
||||
|
||||
This is a package for GO which can be used to create different types of barcodes.
|
||||
|
||||
## Supported Barcode Types ##
|
||||
* 2 of 5
|
||||
* Aztec Code
|
||||
* Codabar
|
||||
* Code 128
|
||||
* Code 39
|
||||
* Code 93
|
||||
* Datamatrix
|
||||
* EAN 13
|
||||
* EAN 8
|
||||
* PDF 417
|
||||
* QR Code
|
||||
|
||||
## Example ##
|
||||
|
||||
This is a simple example on how to create a QR-Code and write it to a png-file
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/png"
|
||||
"os"
|
||||
|
||||
"github.com/boombuler/barcode"
|
||||
"github.com/boombuler/barcode/qr"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create the barcode
|
||||
qrCode, _ := qr.Encode("Hello World", qr.M, qr.Auto)
|
||||
|
||||
// Scale the barcode to 200x200 pixels
|
||||
qrCode, _ = barcode.Scale(qrCode, 200, 200)
|
||||
|
||||
// create the output file
|
||||
file, _ := os.Create("qrcode.png")
|
||||
defer file.Close()
|
||||
|
||||
// encode the barcode as png
|
||||
png.Encode(file, qrCode)
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation ##
|
||||
See [GoDoc](https://godoc.org/github.com/boombuler/barcode)
|
||||
|
||||
To create a barcode use the Encode function from one of the subpackages.
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package barcode
|
||||
|
||||
import "image"
|
||||
|
||||
const (
|
||||
TypeAztec = "Aztec"
|
||||
TypeCodabar = "Codabar"
|
||||
TypeCode128 = "Code 128"
|
||||
TypeCode39 = "Code 39"
|
||||
TypeCode93 = "Code 93"
|
||||
TypeDataMatrix = "DataMatrix"
|
||||
TypeEAN8 = "EAN 8"
|
||||
TypeEAN13 = "EAN 13"
|
||||
TypePDF = "PDF417"
|
||||
TypeQR = "QR Code"
|
||||
Type2of5 = "2 of 5"
|
||||
Type2of5Interleaved = "2 of 5 (interleaved)"
|
||||
)
|
||||
|
||||
// Contains some meta information about a barcode
|
||||
type Metadata struct {
|
||||
// the name of the barcode kind
|
||||
CodeKind string
|
||||
// contains 1 for 1D barcodes or 2 for 2D barcodes
|
||||
Dimensions byte
|
||||
}
|
||||
|
||||
// a rendered and encoded barcode
|
||||
type Barcode interface {
|
||||
image.Image
|
||||
// returns some meta information about the barcode
|
||||
Metadata() Metadata
|
||||
// the data that was encoded in this barcode
|
||||
Content() string
|
||||
}
|
||||
|
||||
// Additional interface that some barcodes might implement to provide
|
||||
// the value of its checksum.
|
||||
type BarcodeIntCS interface {
|
||||
Barcode
|
||||
CheckSum() int
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
const charSet string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
|
||||
|
||||
func stringToAlphaIdx(content string) <-chan int {
|
||||
result := make(chan int)
|
||||
go func() {
|
||||
for _, r := range content {
|
||||
idx := strings.IndexRune(charSet, r)
|
||||
result <- idx
|
||||
if idx < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func encodeAlphaNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
||||
|
||||
contentLenIsOdd := len(content)%2 == 1
|
||||
contentBitCount := (len(content) / 2) * 11
|
||||
if contentLenIsOdd {
|
||||
contentBitCount += 6
|
||||
}
|
||||
vi := findSmallestVersionInfo(ecl, alphaNumericMode, contentBitCount)
|
||||
if vi == nil {
|
||||
return nil, nil, errors.New("To much data to encode")
|
||||
}
|
||||
|
||||
res := new(utils.BitList)
|
||||
res.AddBits(int(alphaNumericMode), 4)
|
||||
res.AddBits(len(content), vi.charCountBits(alphaNumericMode))
|
||||
|
||||
encoder := stringToAlphaIdx(content)
|
||||
|
||||
for idx := 0; idx < len(content)/2; idx++ {
|
||||
c1 := <-encoder
|
||||
c2 := <-encoder
|
||||
if c1 < 0 || c2 < 0 {
|
||||
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
|
||||
}
|
||||
res.AddBits(c1*45+c2, 11)
|
||||
}
|
||||
if contentLenIsOdd {
|
||||
c := <-encoder
|
||||
if c < 0 {
|
||||
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
|
||||
}
|
||||
res.AddBits(c, 6)
|
||||
}
|
||||
|
||||
addPaddingAndTerminator(res, vi)
|
||||
|
||||
return res, vi, nil
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
func encodeAuto(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
||||
bits, vi, _ := Numeric.getEncoder()(content, ecl)
|
||||
if bits != nil && vi != nil {
|
||||
return bits, vi, nil
|
||||
}
|
||||
bits, vi, _ = AlphaNumeric.getEncoder()(content, ecl)
|
||||
if bits != nil && vi != nil {
|
||||
return bits, vi, nil
|
||||
}
|
||||
bits, vi, _ = Unicode.getEncoder()(content, ecl)
|
||||
if bits != nil && vi != nil {
|
||||
return bits, vi, nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("No encoding found to encode \"%s\"", content)
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package qr
|
||||
|
||||
type block struct {
|
||||
data []byte
|
||||
ecc []byte
|
||||
}
|
||||
type blockList []*block
|
||||
|
||||
func splitToBlocks(data <-chan byte, vi *versionInfo) blockList {
|
||||
result := make(blockList, vi.NumberOfBlocksInGroup1+vi.NumberOfBlocksInGroup2)
|
||||
|
||||
for b := 0; b < int(vi.NumberOfBlocksInGroup1); b++ {
|
||||
blk := new(block)
|
||||
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup1)
|
||||
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup1); cw++ {
|
||||
blk.data[cw] = <-data
|
||||
}
|
||||
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
|
||||
result[b] = blk
|
||||
}
|
||||
|
||||
for b := 0; b < int(vi.NumberOfBlocksInGroup2); b++ {
|
||||
blk := new(block)
|
||||
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup2)
|
||||
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup2); cw++ {
|
||||
blk.data[cw] = <-data
|
||||
}
|
||||
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
|
||||
result[int(vi.NumberOfBlocksInGroup1)+b] = blk
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (bl blockList) interleave(vi *versionInfo) []byte {
|
||||
var maxCodewordCount int
|
||||
if vi.DataCodeWordsPerBlockInGroup1 > vi.DataCodeWordsPerBlockInGroup2 {
|
||||
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup1)
|
||||
} else {
|
||||
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup2)
|
||||
}
|
||||
resultLen := (vi.DataCodeWordsPerBlockInGroup1+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup1 +
|
||||
(vi.DataCodeWordsPerBlockInGroup2+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup2
|
||||
|
||||
result := make([]byte, 0, resultLen)
|
||||
for i := 0; i < maxCodewordCount; i++ {
|
||||
for b := 0; b < len(bl); b++ {
|
||||
if len(bl[b].data) > i {
|
||||
result = append(result, bl[b].data[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 0; i < int(vi.ErrorCorrectionCodewordsPerBlock); i++ {
|
||||
for b := 0; b < len(bl); b++ {
|
||||
result = append(result, bl[b].ecc[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
// Package qr can be used to create QR barcodes.
|
||||
package qr
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/boombuler/barcode"
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
type encodeFn func(content string, eccLevel ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error)
|
||||
|
||||
// Encoding mode for QR Codes.
|
||||
type Encoding byte
|
||||
|
||||
const (
|
||||
// Auto will choose ths best matching encoding
|
||||
Auto Encoding = iota
|
||||
// Numeric encoding only encodes numbers [0-9]
|
||||
Numeric
|
||||
// AlphaNumeric encoding only encodes uppercase letters, numbers and [Space], $, %, *, +, -, ., /, :
|
||||
AlphaNumeric
|
||||
// Unicode encoding encodes the string as utf-8
|
||||
Unicode
|
||||
// only for testing purpose
|
||||
unknownEncoding
|
||||
)
|
||||
|
||||
func (e Encoding) getEncoder() encodeFn {
|
||||
switch e {
|
||||
case Auto:
|
||||
return encodeAuto
|
||||
case Numeric:
|
||||
return encodeNumeric
|
||||
case AlphaNumeric:
|
||||
return encodeAlphaNumeric
|
||||
case Unicode:
|
||||
return encodeUnicode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Encoding) String() string {
|
||||
switch e {
|
||||
case Auto:
|
||||
return "Auto"
|
||||
case Numeric:
|
||||
return "Numeric"
|
||||
case AlphaNumeric:
|
||||
return "AlphaNumeric"
|
||||
case Unicode:
|
||||
return "Unicode"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Encode returns a QR barcode with the given content, error correction level and uses the given encoding
|
||||
func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) {
|
||||
bits, vi, err := mode.getEncoder()(content, level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blocks := splitToBlocks(bits.IterateBytes(), vi)
|
||||
data := blocks.interleave(vi)
|
||||
result := render(data, vi)
|
||||
result.content = content
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func render(data []byte, vi *versionInfo) *qrcode {
|
||||
dim := vi.modulWidth()
|
||||
results := make([]*qrcode, 8)
|
||||
for i := 0; i < 8; i++ {
|
||||
results[i] = newBarcode(dim)
|
||||
}
|
||||
|
||||
occupied := newBarcode(dim)
|
||||
|
||||
setAll := func(x int, y int, val bool) {
|
||||
occupied.Set(x, y, true)
|
||||
for i := 0; i < 8; i++ {
|
||||
results[i].Set(x, y, val)
|
||||
}
|
||||
}
|
||||
|
||||
drawFinderPatterns(vi, setAll)
|
||||
drawAlignmentPatterns(occupied, vi, setAll)
|
||||
|
||||
//Timing Pattern:
|
||||
var i int
|
||||
for i = 0; i < dim; i++ {
|
||||
if !occupied.Get(i, 6) {
|
||||
setAll(i, 6, i%2 == 0)
|
||||
}
|
||||
if !occupied.Get(6, i) {
|
||||
setAll(6, i, i%2 == 0)
|
||||
}
|
||||
}
|
||||
// Dark Module
|
||||
setAll(8, dim-8, true)
|
||||
|
||||
drawVersionInfo(vi, setAll)
|
||||
drawFormatInfo(vi, -1, occupied.Set)
|
||||
for i := 0; i < 8; i++ {
|
||||
drawFormatInfo(vi, i, results[i].Set)
|
||||
}
|
||||
|
||||
// Write the data
|
||||
var curBitNo int
|
||||
|
||||
for pos := range iterateModules(occupied) {
|
||||
var curBit bool
|
||||
if curBitNo < len(data)*8 {
|
||||
curBit = ((data[curBitNo/8] >> uint(7-(curBitNo%8))) & 1) == 1
|
||||
} else {
|
||||
curBit = false
|
||||
}
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
setMasked(pos.X, pos.Y, curBit, i, results[i].Set)
|
||||
}
|
||||
curBitNo++
|
||||
}
|
||||
|
||||
lowestPenalty := ^uint(0)
|
||||
lowestPenaltyIdx := -1
|
||||
for i := 0; i < 8; i++ {
|
||||
p := results[i].calcPenalty()
|
||||
if p < lowestPenalty {
|
||||
lowestPenalty = p
|
||||
lowestPenaltyIdx = i
|
||||
}
|
||||
}
|
||||
return results[lowestPenaltyIdx]
|
||||
}
|
||||
|
||||
func setMasked(x, y int, val bool, mask int, set func(int, int, bool)) {
|
||||
switch mask {
|
||||
case 0:
|
||||
val = val != (((y + x) % 2) == 0)
|
||||
break
|
||||
case 1:
|
||||
val = val != ((y % 2) == 0)
|
||||
break
|
||||
case 2:
|
||||
val = val != ((x % 3) == 0)
|
||||
break
|
||||
case 3:
|
||||
val = val != (((y + x) % 3) == 0)
|
||||
break
|
||||
case 4:
|
||||
val = val != (((y/2 + x/3) % 2) == 0)
|
||||
break
|
||||
case 5:
|
||||
val = val != (((y*x)%2)+((y*x)%3) == 0)
|
||||
break
|
||||
case 6:
|
||||
val = val != ((((y*x)%2)+((y*x)%3))%2 == 0)
|
||||
break
|
||||
case 7:
|
||||
val = val != ((((y+x)%2)+((y*x)%3))%2 == 0)
|
||||
}
|
||||
set(x, y, val)
|
||||
}
|
||||
|
||||
func iterateModules(occupied *qrcode) <-chan image.Point {
|
||||
result := make(chan image.Point)
|
||||
allPoints := make(chan image.Point)
|
||||
go func() {
|
||||
curX := occupied.dimension - 1
|
||||
curY := occupied.dimension - 1
|
||||
isUpward := true
|
||||
|
||||
for true {
|
||||
if isUpward {
|
||||
allPoints <- image.Pt(curX, curY)
|
||||
allPoints <- image.Pt(curX-1, curY)
|
||||
curY--
|
||||
if curY < 0 {
|
||||
curY = 0
|
||||
curX -= 2
|
||||
if curX == 6 {
|
||||
curX--
|
||||
}
|
||||
if curX < 0 {
|
||||
break
|
||||
}
|
||||
isUpward = false
|
||||
}
|
||||
} else {
|
||||
allPoints <- image.Pt(curX, curY)
|
||||
allPoints <- image.Pt(curX-1, curY)
|
||||
curY++
|
||||
if curY >= occupied.dimension {
|
||||
curY = occupied.dimension - 1
|
||||
curX -= 2
|
||||
if curX == 6 {
|
||||
curX--
|
||||
}
|
||||
isUpward = true
|
||||
if curX < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(allPoints)
|
||||
}()
|
||||
go func() {
|
||||
for pt := range allPoints {
|
||||
if !occupied.Get(pt.X, pt.Y) {
|
||||
result <- pt
|
||||
}
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
func drawFinderPatterns(vi *versionInfo, set func(int, int, bool)) {
|
||||
dim := vi.modulWidth()
|
||||
drawPattern := func(xoff int, yoff int) {
|
||||
for x := -1; x < 8; x++ {
|
||||
for y := -1; y < 8; y++ {
|
||||
val := (x == 0 || x == 6 || y == 0 || y == 6 || (x > 1 && x < 5 && y > 1 && y < 5)) && (x <= 6 && y <= 6 && x >= 0 && y >= 0)
|
||||
|
||||
if x+xoff >= 0 && x+xoff < dim && y+yoff >= 0 && y+yoff < dim {
|
||||
set(x+xoff, y+yoff, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drawPattern(0, 0)
|
||||
drawPattern(0, dim-7)
|
||||
drawPattern(dim-7, 0)
|
||||
}
|
||||
|
||||
func drawAlignmentPatterns(occupied *qrcode, vi *versionInfo, set func(int, int, bool)) {
|
||||
drawPattern := func(xoff int, yoff int) {
|
||||
for x := -2; x <= 2; x++ {
|
||||
for y := -2; y <= 2; y++ {
|
||||
val := x == -2 || x == 2 || y == -2 || y == 2 || (x == 0 && y == 0)
|
||||
set(x+xoff, y+yoff, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
positions := vi.alignmentPatternPlacements()
|
||||
|
||||
for _, x := range positions {
|
||||
for _, y := range positions {
|
||||
if occupied.Get(x, y) {
|
||||
continue
|
||||
}
|
||||
drawPattern(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var formatInfos = map[ErrorCorrectionLevel]map[int][]bool{
|
||||
L: {
|
||||
0: []bool{true, true, true, false, true, true, true, true, true, false, false, false, true, false, false},
|
||||
1: []bool{true, true, true, false, false, true, false, true, true, true, true, false, false, true, true},
|
||||
2: []bool{true, true, true, true, true, false, true, true, false, true, false, true, false, true, false},
|
||||
3: []bool{true, true, true, true, false, false, false, true, false, false, true, true, true, false, true},
|
||||
4: []bool{true, true, false, false, true, true, false, false, false, true, false, true, true, true, true},
|
||||
5: []bool{true, true, false, false, false, true, true, false, false, false, true, true, false, false, false},
|
||||
6: []bool{true, true, false, true, true, false, false, false, true, false, false, false, false, false, true},
|
||||
7: []bool{true, true, false, true, false, false, true, false, true, true, true, false, true, true, false},
|
||||
},
|
||||
M: {
|
||||
0: []bool{true, false, true, false, true, false, false, false, false, false, true, false, false, true, false},
|
||||
1: []bool{true, false, true, false, false, false, true, false, false, true, false, false, true, false, true},
|
||||
2: []bool{true, false, true, true, true, true, false, false, true, true, true, true, true, false, false},
|
||||
3: []bool{true, false, true, true, false, true, true, false, true, false, false, true, false, true, true},
|
||||
4: []bool{true, false, false, false, true, false, true, true, true, true, true, true, false, false, true},
|
||||
5: []bool{true, false, false, false, false, false, false, true, true, false, false, true, true, true, false},
|
||||
6: []bool{true, false, false, true, true, true, true, true, false, false, true, false, true, true, true},
|
||||
7: []bool{true, false, false, true, false, true, false, true, false, true, false, false, false, false, false},
|
||||
},
|
||||
Q: {
|
||||
0: []bool{false, true, true, false, true, false, true, false, true, false, true, true, true, true, true},
|
||||
1: []bool{false, true, true, false, false, false, false, false, true, true, false, true, false, false, false},
|
||||
2: []bool{false, true, true, true, true, true, true, false, false, true, true, false, false, false, true},
|
||||
3: []bool{false, true, true, true, false, true, false, false, false, false, false, false, true, true, false},
|
||||
4: []bool{false, true, false, false, true, false, false, true, false, true, true, false, true, false, false},
|
||||
5: []bool{false, true, false, false, false, false, true, true, false, false, false, false, false, true, true},
|
||||
6: []bool{false, true, false, true, true, true, false, true, true, false, true, true, false, true, false},
|
||||
7: []bool{false, true, false, true, false, true, true, true, true, true, false, true, true, false, true},
|
||||
},
|
||||
H: {
|
||||
0: []bool{false, false, true, false, true, true, false, true, false, false, false, true, false, false, true},
|
||||
1: []bool{false, false, true, false, false, true, true, true, false, true, true, true, true, true, false},
|
||||
2: []bool{false, false, true, true, true, false, false, true, true, true, false, false, true, true, true},
|
||||
3: []bool{false, false, true, true, false, false, true, true, true, false, true, false, false, false, false},
|
||||
4: []bool{false, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
|
||||
5: []bool{false, false, false, false, false, true, false, false, true, false, true, false, true, false, true},
|
||||
6: []bool{false, false, false, true, true, false, true, false, false, false, false, true, true, false, false},
|
||||
7: []bool{false, false, false, true, false, false, false, false, false, true, true, true, false, true, true},
|
||||
},
|
||||
}
|
||||
|
||||
func drawFormatInfo(vi *versionInfo, usedMask int, set func(int, int, bool)) {
|
||||
var formatInfo []bool
|
||||
|
||||
if usedMask == -1 {
|
||||
formatInfo = []bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true} // Set all to true cause -1 --> occupied mask.
|
||||
} else {
|
||||
formatInfo = formatInfos[vi.Level][usedMask]
|
||||
}
|
||||
|
||||
if len(formatInfo) == 15 {
|
||||
dim := vi.modulWidth()
|
||||
set(0, 8, formatInfo[0])
|
||||
set(1, 8, formatInfo[1])
|
||||
set(2, 8, formatInfo[2])
|
||||
set(3, 8, formatInfo[3])
|
||||
set(4, 8, formatInfo[4])
|
||||
set(5, 8, formatInfo[5])
|
||||
set(7, 8, formatInfo[6])
|
||||
set(8, 8, formatInfo[7])
|
||||
set(8, 7, formatInfo[8])
|
||||
set(8, 5, formatInfo[9])
|
||||
set(8, 4, formatInfo[10])
|
||||
set(8, 3, formatInfo[11])
|
||||
set(8, 2, formatInfo[12])
|
||||
set(8, 1, formatInfo[13])
|
||||
set(8, 0, formatInfo[14])
|
||||
|
||||
set(8, dim-1, formatInfo[0])
|
||||
set(8, dim-2, formatInfo[1])
|
||||
set(8, dim-3, formatInfo[2])
|
||||
set(8, dim-4, formatInfo[3])
|
||||
set(8, dim-5, formatInfo[4])
|
||||
set(8, dim-6, formatInfo[5])
|
||||
set(8, dim-7, formatInfo[6])
|
||||
set(dim-8, 8, formatInfo[7])
|
||||
set(dim-7, 8, formatInfo[8])
|
||||
set(dim-6, 8, formatInfo[9])
|
||||
set(dim-5, 8, formatInfo[10])
|
||||
set(dim-4, 8, formatInfo[11])
|
||||
set(dim-3, 8, formatInfo[12])
|
||||
set(dim-2, 8, formatInfo[13])
|
||||
set(dim-1, 8, formatInfo[14])
|
||||
}
|
||||
}
|
||||
|
||||
var versionInfoBitsByVersion = map[byte][]bool{
|
||||
7: []bool{false, false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false},
|
||||
8: []bool{false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false},
|
||||
9: []bool{false, false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true},
|
||||
10: []bool{false, false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true},
|
||||
11: []bool{false, false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false},
|
||||
12: []bool{false, false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
|
||||
13: []bool{false, false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true},
|
||||
14: []bool{false, false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true},
|
||||
15: []bool{false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false},
|
||||
16: []bool{false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false},
|
||||
17: []bool{false, true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true},
|
||||
18: []bool{false, true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true},
|
||||
19: []bool{false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false},
|
||||
20: []bool{false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true, false},
|
||||
21: []bool{false, true, false, true, false, true, false, true, true, false, true, false, false, false, false, false, true, true},
|
||||
22: []bool{false, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false, false, true},
|
||||
23: []bool{false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false, false},
|
||||
24: []bool{false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false, false},
|
||||
25: []bool{false, true, true, false, false, true, false, false, false, true, true, true, true, false, false, false, false, true},
|
||||
26: []bool{false, true, true, false, true, false, true, true, true, true, true, false, true, false, true, false, true, true},
|
||||
27: []bool{false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true, false},
|
||||
28: []bool{false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false},
|
||||
29: []bool{false, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true},
|
||||
30: []bool{false, true, true, true, true, false, true, true, false, true, false, true, true, true, false, true, false, true},
|
||||
31: []bool{false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false, false},
|
||||
32: []bool{true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, true, false, true},
|
||||
33: []bool{true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false, false},
|
||||
34: []bool{true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true, false},
|
||||
35: []bool{true, false, false, false, true, true, false, true, true, true, true, false, false, true, true, true, true, true},
|
||||
36: []bool{true, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true},
|
||||
37: []bool{true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false},
|
||||
38: []bool{true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false},
|
||||
39: []bool{true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false, false, true},
|
||||
40: []bool{true, false, true, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true},
|
||||
}
|
||||
|
||||
func drawVersionInfo(vi *versionInfo, set func(int, int, bool)) {
|
||||
versionInfoBits, ok := versionInfoBitsByVersion[vi.Version]
|
||||
|
||||
if ok && len(versionInfoBits) > 0 {
|
||||
for i := 0; i < len(versionInfoBits); i++ {
|
||||
x := (vi.modulWidth() - 11) + i%3
|
||||
y := i / 3
|
||||
set(x, y, versionInfoBits[len(versionInfoBits)-i-1])
|
||||
set(y, x, versionInfoBits[len(versionInfoBits)-i-1])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func addPaddingAndTerminator(bl *utils.BitList, vi *versionInfo) {
|
||||
for i := 0; i < 4 && bl.Len() < vi.totalDataBytes()*8; i++ {
|
||||
bl.AddBit(false)
|
||||
}
|
||||
|
||||
for bl.Len()%8 != 0 {
|
||||
bl.AddBit(false)
|
||||
}
|
||||
|
||||
for i := 0; bl.Len() < vi.totalDataBytes()*8; i++ {
|
||||
if i%2 == 0 {
|
||||
bl.AddByte(236)
|
||||
} else {
|
||||
bl.AddByte(17)
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
type errorCorrection struct {
|
||||
rs *utils.ReedSolomonEncoder
|
||||
}
|
||||
|
||||
var ec = newErrorCorrection()
|
||||
|
||||
func newErrorCorrection() *errorCorrection {
|
||||
fld := utils.NewGaloisField(285, 256, 0)
|
||||
return &errorCorrection{utils.NewReedSolomonEncoder(fld)}
|
||||
}
|
||||
|
||||
func (ec *errorCorrection) calcECC(data []byte, eccCount byte) []byte {
|
||||
dataInts := make([]int, len(data))
|
||||
for i := 0; i < len(data); i++ {
|
||||
dataInts[i] = int(data[i])
|
||||
}
|
||||
res := ec.rs.Encode(dataInts, int(eccCount))
|
||||
result := make([]byte, len(res))
|
||||
for i := 0; i < len(res); i++ {
|
||||
result[i] = byte(res[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
func encodeNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
||||
contentBitCount := (len(content) / 3) * 10
|
||||
switch len(content) % 3 {
|
||||
case 1:
|
||||
contentBitCount += 4
|
||||
case 2:
|
||||
contentBitCount += 7
|
||||
}
|
||||
vi := findSmallestVersionInfo(ecl, numericMode, contentBitCount)
|
||||
if vi == nil {
|
||||
return nil, nil, errors.New("To much data to encode")
|
||||
}
|
||||
res := new(utils.BitList)
|
||||
res.AddBits(int(numericMode), 4)
|
||||
res.AddBits(len(content), vi.charCountBits(numericMode))
|
||||
|
||||
for pos := 0; pos < len(content); pos += 3 {
|
||||
var curStr string
|
||||
if pos+3 <= len(content) {
|
||||
curStr = content[pos : pos+3]
|
||||
} else {
|
||||
curStr = content[pos:]
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(curStr)
|
||||
if err != nil || i < 0 {
|
||||
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, Numeric)
|
||||
}
|
||||
var bitCnt byte
|
||||
switch len(curStr) % 3 {
|
||||
case 0:
|
||||
bitCnt = 10
|
||||
case 1:
|
||||
bitCnt = 4
|
||||
break
|
||||
case 2:
|
||||
bitCnt = 7
|
||||
break
|
||||
}
|
||||
|
||||
res.AddBits(i, bitCnt)
|
||||
}
|
||||
|
||||
addPaddingAndTerminator(res, vi)
|
||||
return res, vi, nil
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/boombuler/barcode"
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
type qrcode struct {
|
||||
dimension int
|
||||
data *utils.BitList
|
||||
content string
|
||||
}
|
||||
|
||||
func (qr *qrcode) Content() string {
|
||||
return qr.content
|
||||
}
|
||||
|
||||
func (qr *qrcode) Metadata() barcode.Metadata {
|
||||
return barcode.Metadata{barcode.TypeQR, 2}
|
||||
}
|
||||
|
||||
func (qr *qrcode) ColorModel() color.Model {
|
||||
return color.Gray16Model
|
||||
}
|
||||
|
||||
func (qr *qrcode) Bounds() image.Rectangle {
|
||||
return image.Rect(0, 0, qr.dimension, qr.dimension)
|
||||
}
|
||||
|
||||
func (qr *qrcode) At(x, y int) color.Color {
|
||||
if qr.Get(x, y) {
|
||||
return color.Black
|
||||
}
|
||||
return color.White
|
||||
}
|
||||
|
||||
func (qr *qrcode) Get(x, y int) bool {
|
||||
return qr.data.GetBit(x*qr.dimension + y)
|
||||
}
|
||||
|
||||
func (qr *qrcode) Set(x, y int, val bool) {
|
||||
qr.data.SetBit(x*qr.dimension+y, val)
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenalty() uint {
|
||||
return qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4()
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenaltyRule1() uint {
|
||||
var result uint
|
||||
for x := 0; x < qr.dimension; x++ {
|
||||
checkForX := false
|
||||
var cntX uint
|
||||
checkForY := false
|
||||
var cntY uint
|
||||
|
||||
for y := 0; y < qr.dimension; y++ {
|
||||
if qr.Get(x, y) == checkForX {
|
||||
cntX++
|
||||
} else {
|
||||
checkForX = !checkForX
|
||||
if cntX >= 5 {
|
||||
result += cntX - 2
|
||||
}
|
||||
cntX = 1
|
||||
}
|
||||
|
||||
if qr.Get(y, x) == checkForY {
|
||||
cntY++
|
||||
} else {
|
||||
checkForY = !checkForY
|
||||
if cntY >= 5 {
|
||||
result += cntY - 2
|
||||
}
|
||||
cntY = 1
|
||||
}
|
||||
}
|
||||
|
||||
if cntX >= 5 {
|
||||
result += cntX - 2
|
||||
}
|
||||
if cntY >= 5 {
|
||||
result += cntY - 2
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenaltyRule2() uint {
|
||||
var result uint
|
||||
for x := 0; x < qr.dimension-1; x++ {
|
||||
for y := 0; y < qr.dimension-1; y++ {
|
||||
check := qr.Get(x, y)
|
||||
if qr.Get(x, y+1) == check && qr.Get(x+1, y) == check && qr.Get(x+1, y+1) == check {
|
||||
result += 3
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenaltyRule3() uint {
|
||||
pattern1 := []bool{true, false, true, true, true, false, true, false, false, false, false}
|
||||
pattern2 := []bool{false, false, false, false, true, false, true, true, true, false, true}
|
||||
|
||||
var result uint
|
||||
for x := 0; x <= qr.dimension-len(pattern1); x++ {
|
||||
for y := 0; y < qr.dimension; y++ {
|
||||
pattern1XFound := true
|
||||
pattern2XFound := true
|
||||
pattern1YFound := true
|
||||
pattern2YFound := true
|
||||
|
||||
for i := 0; i < len(pattern1); i++ {
|
||||
iv := qr.Get(x+i, y)
|
||||
if iv != pattern1[i] {
|
||||
pattern1XFound = false
|
||||
}
|
||||
if iv != pattern2[i] {
|
||||
pattern2XFound = false
|
||||
}
|
||||
iv = qr.Get(y, x+i)
|
||||
if iv != pattern1[i] {
|
||||
pattern1YFound = false
|
||||
}
|
||||
if iv != pattern2[i] {
|
||||
pattern2YFound = false
|
||||
}
|
||||
}
|
||||
if pattern1XFound || pattern2XFound {
|
||||
result += 40
|
||||
}
|
||||
if pattern1YFound || pattern2YFound {
|
||||
result += 40
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (qr *qrcode) calcPenaltyRule4() uint {
|
||||
totalNum := qr.data.Len()
|
||||
trueCnt := 0
|
||||
for i := 0; i < totalNum; i++ {
|
||||
if qr.data.GetBit(i) {
|
||||
trueCnt++
|
||||
}
|
||||
}
|
||||
percDark := float64(trueCnt) * 100 / float64(totalNum)
|
||||
floor := math.Abs(math.Floor(percDark/5) - 10)
|
||||
ceil := math.Abs(math.Ceil(percDark/5) - 10)
|
||||
return uint(math.Min(floor, ceil) * 10)
|
||||
}
|
||||
|
||||
func newBarcode(dim int) *qrcode {
|
||||
res := new(qrcode)
|
||||
res.dimension = dim
|
||||
res.data = utils.NewBitList(dim * dim)
|
||||
return res
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package qr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/boombuler/barcode/utils"
|
||||
)
|
||||
|
||||
func encodeUnicode(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
||||
data := []byte(content)
|
||||
|
||||
vi := findSmallestVersionInfo(ecl, byteMode, len(data)*8)
|
||||
if vi == nil {
|
||||
return nil, nil, errors.New("To much data to encode")
|
||||
}
|
||||
|
||||
// It's not correct to add the unicode bytes to the result directly but most readers can't handle the
|
||||
// required ECI header...
|
||||
res := new(utils.BitList)
|
||||
res.AddBits(int(byteMode), 4)
|
||||
res.AddBits(len(content), vi.charCountBits(byteMode))
|
||||
for _, b := range data {
|
||||
res.AddByte(b)
|
||||
}
|
||||
addPaddingAndTerminator(res, vi)
|
||||
return res, vi, nil
|
||||
}
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
package qr
|
||||
|
||||
import "math"
|
||||
|
||||
// ErrorCorrectionLevel indicates the amount of "backup data" stored in the QR code
|
||||
type ErrorCorrectionLevel byte
|
||||
|
||||
const (
|
||||
// L recovers 7% of data
|
||||
L ErrorCorrectionLevel = iota
|
||||
// M recovers 15% of data
|
||||
M
|
||||
// Q recovers 25% of data
|
||||
Q
|
||||
// H recovers 30% of data
|
||||
H
|
||||
)
|
||||
|
||||
func (ecl ErrorCorrectionLevel) String() string {
|
||||
switch ecl {
|
||||
case L:
|
||||
return "L"
|
||||
case M:
|
||||
return "M"
|
||||
case Q:
|
||||
return "Q"
|
||||
case H:
|
||||
return "H"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
type encodingMode byte
|
||||
|
||||
const (
|
||||
numericMode encodingMode = 1
|
||||
alphaNumericMode encodingMode = 2
|
||||
byteMode encodingMode = 4
|
||||
kanjiMode encodingMode = 8
|
||||
)
|
||||
|
||||
type versionInfo struct {
|
||||
Version byte
|
||||
Level ErrorCorrectionLevel
|
||||
ErrorCorrectionCodewordsPerBlock byte
|
||||
NumberOfBlocksInGroup1 byte
|
||||
DataCodeWordsPerBlockInGroup1 byte
|
||||
NumberOfBlocksInGroup2 byte
|
||||
DataCodeWordsPerBlockInGroup2 byte
|
||||
}
|
||||
|
||||
var versionInfos = []*versionInfo{
|
||||
&versionInfo{1, L, 7, 1, 19, 0, 0},
|
||||
&versionInfo{1, M, 10, 1, 16, 0, 0},
|
||||
&versionInfo{1, Q, 13, 1, 13, 0, 0},
|
||||
&versionInfo{1, H, 17, 1, 9, 0, 0},
|
||||
&versionInfo{2, L, 10, 1, 34, 0, 0},
|
||||
&versionInfo{2, M, 16, 1, 28, 0, 0},
|
||||
&versionInfo{2, Q, 22, 1, 22, 0, 0},
|
||||
&versionInfo{2, H, 28, 1, 16, 0, 0},
|
||||
&versionInfo{3, L, 15, 1, 55, 0, 0},
|
||||
&versionInfo{3, M, 26, 1, 44, 0, 0},
|
||||
&versionInfo{3, Q, 18, 2, 17, 0, 0},
|
||||
&versionInfo{3, H, 22, 2, 13, 0, 0},
|
||||
&versionInfo{4, L, 20, 1, 80, 0, 0},
|
||||
&versionInfo{4, M, 18, 2, 32, 0, 0},
|
||||
&versionInfo{4, Q, 26, 2, 24, 0, 0},
|
||||
&versionInfo{4, H, 16, 4, 9, 0, 0},
|
||||
&versionInfo{5, L, 26, 1, 108, 0, 0},
|
||||
&versionInfo{5, M, 24, 2, 43, 0, 0},
|
||||
&versionInfo{5, Q, 18, 2, 15, 2, 16},
|
||||
&versionInfo{5, H, 22, 2, 11, 2, 12},
|
||||
&versionInfo{6, L, 18, 2, 68, 0, 0},
|
||||
&versionInfo{6, M, 16, 4, 27, 0, 0},
|
||||
&versionInfo{6, Q, 24, 4, 19, 0, 0},
|
||||
&versionInfo{6, H, 28, 4, 15, 0, 0},
|
||||
&versionInfo{7, L, 20, 2, 78, 0, 0},
|
||||
&versionInfo{7, M, 18, 4, 31, 0, 0},
|
||||
&versionInfo{7, Q, 18, 2, 14, 4, 15},
|
||||
&versionInfo{7, H, 26, 4, 13, 1, 14},
|
||||
&versionInfo{8, L, 24, 2, 97, 0, 0},
|
||||
&versionInfo{8, M, 22, 2, 38, 2, 39},
|
||||
&versionInfo{8, Q, 22, 4, 18, 2, 19},
|
||||
&versionInfo{8, H, 26, 4, 14, 2, 15},
|
||||
&versionInfo{9, L, 30, 2, 116, 0, 0},
|
||||
&versionInfo{9, M, 22, 3, 36, 2, 37},
|
||||
&versionInfo{9, Q, 20, 4, 16, 4, 17},
|
||||
&versionInfo{9, H, 24, 4, 12, 4, 13},
|
||||
&versionInfo{10, L, 18, 2, 68, 2, 69},
|
||||
&versionInfo{10, M, 26, 4, 43, 1, 44},
|
||||
&versionInfo{10, Q, 24, 6, 19, 2, 20},
|
||||
&versionInfo{10, H, 28, 6, 15, 2, 16},
|
||||
&versionInfo{11, L, 20, 4, 81, 0, 0},
|
||||
&versionInfo{11, M, 30, 1, 50, 4, 51},
|
||||
&versionInfo{11, Q, 28, 4, 22, 4, 23},
|
||||
&versionInfo{11, H, 24, 3, 12, 8, 13},
|
||||
&versionInfo{12, L, 24, 2, 92, 2, 93},
|
||||
&versionInfo{12, M, 22, 6, 36, 2, 37},
|
||||
&versionInfo{12, Q, 26, 4, 20, 6, 21},
|
||||
&versionInfo{12, H, 28, 7, 14, 4, 15},
|
||||
&versionInfo{13, L, 26, 4, 107, 0, 0},
|
||||
&versionInfo{13, M, 22, 8, 37, 1, 38},
|
||||
&versionInfo{13, Q, 24, 8, 20, 4, 21},
|
||||
&versionInfo{13, H, 22, 12, 11, 4, 12},
|
||||
&versionInfo{14, L, 30, 3, 115, 1, 116},
|
||||
&versionInfo{14, M, 24, 4, 40, 5, 41},
|
||||
&versionInfo{14, Q, 20, 11, 16, 5, 17},
|
||||
&versionInfo{14, H, 24, 11, 12, 5, 13},
|
||||
&versionInfo{15, L, 22, 5, 87, 1, 88},
|
||||
&versionInfo{15, M, 24, 5, 41, 5, 42},
|
||||
&versionInfo{15, Q, 30, 5, 24, 7, 25},
|
||||
&versionInfo{15, H, 24, 11, 12, 7, 13},
|
||||
&versionInfo{16, L, 24, 5, 98, 1, 99},
|
||||
&versionInfo{16, M, 28, 7, 45, 3, 46},
|
||||
&versionInfo{16, Q, 24, 15, 19, 2, 20},
|
||||
&versionInfo{16, H, 30, 3, 15, 13, 16},
|
||||
&versionInfo{17, L, 28, 1, 107, 5, 108},
|
||||
&versionInfo{17, M, 28, 10, 46, 1, 47},
|
||||
&versionInfo{17, Q, 28, 1, 22, 15, 23},
|
||||
&versionInfo{17, H, 28, 2, 14, 17, 15},
|
||||
&versionInfo{18, L, 30, 5, 120, 1, 121},
|
||||
&versionInfo{18, M, 26, 9, 43, 4, 44},
|
||||
&versionInfo{18, Q, 28, 17, 22, 1, 23},
|
||||
&versionInfo{18, H, 28, 2, 14, 19, 15},
|
||||
&versionInfo{19, L, 28, 3, 113, 4, 114},
|
||||
&versionInfo{19, M, 26, 3, 44, 11, 45},
|
||||
&versionInfo{19, Q, 26, 17, 21, 4, 22},
|
||||
&versionInfo{19, H, 26, 9, 13, 16, 14},
|
||||
&versionInfo{20, L, 28, 3, 107, 5, 108},
|
||||
&versionInfo{20, M, 26, 3, 41, 13, 42},
|
||||
&versionInfo{20, Q, 30, 15, 24, 5, 25},
|
||||
&versionInfo{20, H, 28, 15, 15, 10, 16},
|
||||
&versionInfo{21, L, 28, 4, 116, 4, 117},
|
||||
&versionInfo{21, M, 26, 17, 42, 0, 0},
|
||||
&versionInfo{21, Q, 28, 17, 22, 6, 23},
|
||||
&versionInfo{21, H, 30, 19, 16, 6, 17},
|
||||
&versionInfo{22, L, 28, 2, 111, 7, 112},
|
||||
&versionInfo{22, M, 28, 17, 46, 0, 0},
|
||||
&versionInfo{22, Q, 30, 7, 24, 16, 25},
|
||||
&versionInfo{22, H, 24, 34, 13, 0, 0},
|
||||
&versionInfo{23, L, 30, 4, 121, 5, 122},
|
||||
&versionInfo{23, M, 28, 4, 47, 14, 48},
|
||||
&versionInfo{23, Q, 30, 11, 24, 14, 25},
|
||||
&versionInfo{23, H, 30, 16, 15, 14, 16},
|
||||
&versionInfo{24, L, 30, 6, 117, 4, 118},
|
||||
&versionInfo{24, M, 28, 6, 45, 14, 46},
|
||||
&versionInfo{24, Q, 30, 11, 24, 16, 25},
|
||||
&versionInfo{24, H, 30, 30, 16, 2, 17},
|
||||
&versionInfo{25, L, 26, 8, 106, 4, 107},
|
||||
&versionInfo{25, M, 28, 8, 47, 13, 48},
|
||||
&versionInfo{25, Q, 30, 7, 24, 22, 25},
|
||||
&versionInfo{25, H, 30, 22, 15, 13, 16},
|
||||
&versionInfo{26, L, 28, 10, 114, 2, 115},
|
||||
&versionInfo{26, M, 28, 19, 46, 4, 47},
|
||||
&versionInfo{26, Q, 28, 28, 22, 6, 23},
|
||||
&versionInfo{26, H, 30, 33, 16, 4, 17},
|
||||
&versionInfo{27, L, 30, 8, 122, 4, 123},
|
||||
&versionInfo{27, M, 28, 22, 45, 3, 46},
|
||||
&versionInfo{27, Q, 30, 8, 23, 26, 24},
|
||||
&versionInfo{27, H, 30, 12, 15, 28, 16},
|
||||
&versionInfo{28, L, 30, 3, 117, 10, 118},
|
||||
&versionInfo{28, M, 28, 3, 45, 23, 46},
|
||||
&versionInfo{28, Q, 30, 4, 24, 31, 25},
|
||||
&versionInfo{28, H, 30, 11, 15, 31, 16},
|
||||
&versionInfo{29, L, 30, 7, 116, 7, 117},
|
||||
&versionInfo{29, M, 28, 21, 45, 7, 46},
|
||||
&versionInfo{29, Q, 30, 1, 23, 37, 24},
|
||||
&versionInfo{29, H, 30, 19, 15, 26, 16},
|
||||
&versionInfo{30, L, 30, 5, 115, 10, 116},
|
||||
&versionInfo{30, M, 28, 19, 47, 10, 48},
|
||||
&versionInfo{30, Q, 30, 15, 24, 25, 25},
|
||||
&versionInfo{30, H, 30, 23, 15, 25, 16},
|
||||
&versionInfo{31, L, 30, 13, 115, 3, 116},
|
||||
&versionInfo{31, M, 28, 2, 46, 29, 47},
|
||||
&versionInfo{31, Q, 30, 42, 24, 1, 25},
|
||||
&versionInfo{31, H, 30, 23, 15, 28, 16},
|
||||
&versionInfo{32, L, 30, 17, 115, 0, 0},
|
||||
&versionInfo{32, M, 28, 10, 46, 23, 47},
|
||||
&versionInfo{32, Q, 30, 10, 24, 35, 25},
|
||||
&versionInfo{32, H, 30, 19, 15, 35, 16},
|
||||
&versionInfo{33, L, 30, 17, 115, 1, 116},
|
||||
&versionInfo{33, M, 28, 14, 46, 21, 47},
|
||||
&versionInfo{33, Q, 30, 29, 24, 19, 25},
|
||||
&versionInfo{33, H, 30, 11, 15, 46, 16},
|
||||
&versionInfo{34, L, 30, 13, 115, 6, 116},
|
||||
&versionInfo{34, M, 28, 14, 46, 23, 47},
|
||||
&versionInfo{34, Q, 30, 44, 24, 7, 25},
|
||||
&versionInfo{34, H, 30, 59, 16, 1, 17},
|
||||
&versionInfo{35, L, 30, 12, 121, 7, 122},
|
||||
&versionInfo{35, M, 28, 12, 47, 26, 48},
|
||||
&versionInfo{35, Q, 30, 39, 24, 14, 25},
|
||||
&versionInfo{35, H, 30, 22, 15, 41, 16},
|
||||
&versionInfo{36, L, 30, 6, 121, 14, 122},
|
||||
&versionInfo{36, M, 28, 6, 47, 34, 48},
|
||||
&versionInfo{36, Q, 30, 46, 24, 10, 25},
|
||||
&versionInfo{36, H, 30, 2, 15, 64, 16},
|
||||
&versionInfo{37, L, 30, 17, 122, 4, 123},
|
||||
&versionInfo{37, M, 28, 29, 46, 14, 47},
|
||||
&versionInfo{37, Q, 30, 49, 24, 10, 25},
|
||||
&versionInfo{37, H, 30, 24, 15, 46, 16},
|
||||
&versionInfo{38, L, 30, 4, 122, 18, 123},
|
||||
&versionInfo{38, M, 28, 13, 46, 32, 47},
|
||||
&versionInfo{38, Q, 30, 48, 24, 14, 25},
|
||||
&versionInfo{38, H, 30, 42, 15, 32, 16},
|
||||
&versionInfo{39, L, 30, 20, 117, 4, 118},
|
||||
&versionInfo{39, M, 28, 40, 47, 7, 48},
|
||||
&versionInfo{39, Q, 30, 43, 24, 22, 25},
|
||||
&versionInfo{39, H, 30, 10, 15, 67, 16},
|
||||
&versionInfo{40, L, 30, 19, 118, 6, 119},
|
||||
&versionInfo{40, M, 28, 18, 47, 31, 48},
|
||||
&versionInfo{40, Q, 30, 34, 24, 34, 25},
|
||||
&versionInfo{40, H, 30, 20, 15, 61, 16},
|
||||
}
|
||||
|
||||
func (vi *versionInfo) totalDataBytes() int {
|
||||
g1Data := int(vi.NumberOfBlocksInGroup1) * int(vi.DataCodeWordsPerBlockInGroup1)
|
||||
g2Data := int(vi.NumberOfBlocksInGroup2) * int(vi.DataCodeWordsPerBlockInGroup2)
|
||||
return (g1Data + g2Data)
|
||||
}
|
||||
|
||||
func (vi *versionInfo) charCountBits(m encodingMode) byte {
|
||||
switch m {
|
||||
case numericMode:
|
||||
if vi.Version < 10 {
|
||||
return 10
|
||||
} else if vi.Version < 27 {
|
||||
return 12
|
||||
}
|
||||
return 14
|
||||
|
||||
case alphaNumericMode:
|
||||
if vi.Version < 10 {
|
||||
return 9
|
||||
} else if vi.Version < 27 {
|
||||
return 11
|
||||
}
|
||||
return 13
|
||||
|
||||
case byteMode:
|
||||
if vi.Version < 10 {
|
||||
return 8
|
||||
}
|
||||
return 16
|
||||
|
||||
case kanjiMode:
|
||||
if vi.Version < 10 {
|
||||
return 8
|
||||
} else if vi.Version < 27 {
|
||||
return 10
|
||||
}
|
||||
return 12
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (vi *versionInfo) modulWidth() int {
|
||||
return ((int(vi.Version) - 1) * 4) + 21
|
||||
}
|
||||
|
||||
func (vi *versionInfo) alignmentPatternPlacements() []int {
|
||||
if vi.Version == 1 {
|
||||
return make([]int, 0)
|
||||
}
|
||||
|
||||
first := 6
|
||||
last := vi.modulWidth() - 7
|
||||
space := float64(last - first)
|
||||
count := int(math.Ceil(space/28)) + 1
|
||||
|
||||
result := make([]int, count)
|
||||
result[0] = first
|
||||
result[len(result)-1] = last
|
||||
if count > 2 {
|
||||
step := int(math.Ceil(float64(last-first) / float64(count-1)))
|
||||
if step%2 == 1 {
|
||||
frac := float64(last-first) / float64(count-1)
|
||||
_, x := math.Modf(frac)
|
||||
if x >= 0.5 {
|
||||
frac = math.Ceil(frac)
|
||||
} else {
|
||||
frac = math.Floor(frac)
|
||||
}
|
||||
|
||||
if int(frac)%2 == 0 {
|
||||
step--
|
||||
} else {
|
||||
step++
|
||||
}
|
||||
}
|
||||
|
||||
for i := 1; i <= count-2; i++ {
|
||||
result[i] = last - (step * (count - 1 - i))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func findSmallestVersionInfo(ecl ErrorCorrectionLevel, mode encodingMode, dataBits int) *versionInfo {
|
||||
dataBits = dataBits + 4 // mode indicator
|
||||
for _, vi := range versionInfos {
|
||||
if vi.Level == ecl {
|
||||
if (vi.totalDataBytes() * 8) >= (dataBits + int(vi.charCountBits(mode))) {
|
||||
return vi
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package barcode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
type wrapFunc func(x, y int) color.Color
|
||||
|
||||
type scaledBarcode struct {
|
||||
wrapped Barcode
|
||||
wrapperFunc wrapFunc
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
type intCSscaledBC struct {
|
||||
scaledBarcode
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) Content() string {
|
||||
return bc.wrapped.Content()
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) Metadata() Metadata {
|
||||
return bc.wrapped.Metadata()
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) ColorModel() color.Model {
|
||||
return bc.wrapped.ColorModel()
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) Bounds() image.Rectangle {
|
||||
return bc.rect
|
||||
}
|
||||
|
||||
func (bc *scaledBarcode) At(x, y int) color.Color {
|
||||
return bc.wrapperFunc(x, y)
|
||||
}
|
||||
|
||||
func (bc *intCSscaledBC) CheckSum() int {
|
||||
if cs, ok := bc.wrapped.(BarcodeIntCS); ok {
|
||||
return cs.CheckSum()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Scale returns a resized barcode with the given width and height.
|
||||
func Scale(bc Barcode, width, height int) (Barcode, error) {
|
||||
switch bc.Metadata().Dimensions {
|
||||
case 1:
|
||||
return scale1DCode(bc, width, height)
|
||||
case 2:
|
||||
return scale2DCode(bc, width, height)
|
||||
}
|
||||
|
||||
return nil, errors.New("unsupported barcode format")
|
||||
}
|
||||
|
||||
func newScaledBC(wrapped Barcode, wrapperFunc wrapFunc, rect image.Rectangle) Barcode {
|
||||
result := &scaledBarcode{
|
||||
wrapped: wrapped,
|
||||
wrapperFunc: wrapperFunc,
|
||||
rect: rect,
|
||||
}
|
||||
|
||||
if _, ok := wrapped.(BarcodeIntCS); ok {
|
||||
return &intCSscaledBC{*result}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func scale2DCode(bc Barcode, width, height int) (Barcode, error) {
|
||||
orgBounds := bc.Bounds()
|
||||
orgWidth := orgBounds.Max.X - orgBounds.Min.X
|
||||
orgHeight := orgBounds.Max.Y - orgBounds.Min.Y
|
||||
|
||||
factor := int(math.Min(float64(width)/float64(orgWidth), float64(height)/float64(orgHeight)))
|
||||
if factor <= 0 {
|
||||
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx%d", orgWidth, orgHeight)
|
||||
}
|
||||
|
||||
offsetX := (width - (orgWidth * factor)) / 2
|
||||
offsetY := (height - (orgHeight * factor)) / 2
|
||||
|
||||
wrap := func(x, y int) color.Color {
|
||||
if x < offsetX || y < offsetY {
|
||||
return color.White
|
||||
}
|
||||
x = (x - offsetX) / factor
|
||||
y = (y - offsetY) / factor
|
||||
if x >= orgWidth || y >= orgHeight {
|
||||
return color.White
|
||||
}
|
||||
return bc.At(x, y)
|
||||
}
|
||||
|
||||
return newScaledBC(
|
||||
bc,
|
||||
wrap,
|
||||
image.Rect(0, 0, width, height),
|
||||
), nil
|
||||
}
|
||||
|
||||
func scale1DCode(bc Barcode, width, height int) (Barcode, error) {
|
||||
orgBounds := bc.Bounds()
|
||||
orgWidth := orgBounds.Max.X - orgBounds.Min.X
|
||||
factor := int(float64(width) / float64(orgWidth))
|
||||
|
||||
if factor <= 0 {
|
||||
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx1", orgWidth)
|
||||
}
|
||||
offsetX := (width - (orgWidth * factor)) / 2
|
||||
|
||||
wrap := func(x, y int) color.Color {
|
||||
if x < offsetX {
|
||||
return color.White
|
||||
}
|
||||
x = (x - offsetX) / factor
|
||||
|
||||
if x >= orgWidth {
|
||||
return color.White
|
||||
}
|
||||
return bc.At(x, 0)
|
||||
}
|
||||
|
||||
return newScaledBC(
|
||||
bc,
|
||||
wrap,
|
||||
image.Rect(0, 0, width, height),
|
||||
), nil
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Package utils contain some utilities which are needed to create barcodes
|
||||
package utils
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/boombuler/barcode"
|
||||
)
|
||||
|
||||
type base1DCode struct {
|
||||
*BitList
|
||||
kind string
|
||||
content string
|
||||
}
|
||||
|
||||
type base1DCodeIntCS struct {
|
||||
base1DCode
|
||||
checksum int
|
||||
}
|
||||
|
||||
func (c *base1DCode) Content() string {
|
||||
return c.content
|
||||
}
|
||||
|
||||
func (c *base1DCode) Metadata() barcode.Metadata {
|
||||
return barcode.Metadata{c.kind, 1}
|
||||
}
|
||||
|
||||
func (c *base1DCode) ColorModel() color.Model {
|
||||
return color.Gray16Model
|
||||
}
|
||||
|
||||
func (c *base1DCode) Bounds() image.Rectangle {
|
||||
return image.Rect(0, 0, c.Len(), 1)
|
||||
}
|
||||
|
||||
func (c *base1DCode) At(x, y int) color.Color {
|
||||
if c.GetBit(x) {
|
||||
return color.Black
|
||||
}
|
||||
return color.White
|
||||
}
|
||||
|
||||
func (c *base1DCodeIntCS) CheckSum() int {
|
||||
return c.checksum
|
||||
}
|
||||
|
||||
// New1DCodeIntCheckSum creates a new 1D barcode where the bars are represented by the bits in the bars BitList
|
||||
func New1DCodeIntCheckSum(codeKind, content string, bars *BitList, checksum int) barcode.BarcodeIntCS {
|
||||
return &base1DCodeIntCS{base1DCode{bars, codeKind, content}, checksum}
|
||||
}
|
||||
|
||||
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
|
||||
func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode {
|
||||
return &base1DCode{bars, codeKind, content}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package utils
|
||||
|
||||
// BitList is a list that contains bits
|
||||
type BitList struct {
|
||||
count int
|
||||
data []int32
|
||||
}
|
||||
|
||||
// NewBitList returns a new BitList with the given length
|
||||
// all bits are initialize with false
|
||||
func NewBitList(capacity int) *BitList {
|
||||
bl := new(BitList)
|
||||
bl.count = capacity
|
||||
x := 0
|
||||
if capacity%32 != 0 {
|
||||
x = 1
|
||||
}
|
||||
bl.data = make([]int32, capacity/32+x)
|
||||
return bl
|
||||
}
|
||||
|
||||
// Len returns the number of contained bits
|
||||
func (bl *BitList) Len() int {
|
||||
return bl.count
|
||||
}
|
||||
|
||||
func (bl *BitList) grow() {
|
||||
growBy := len(bl.data)
|
||||
if growBy < 128 {
|
||||
growBy = 128
|
||||
} else if growBy >= 1024 {
|
||||
growBy = 1024
|
||||
}
|
||||
|
||||
nd := make([]int32, len(bl.data)+growBy)
|
||||
copy(nd, bl.data)
|
||||
bl.data = nd
|
||||
}
|
||||
|
||||
// AddBit appends the given bits to the end of the list
|
||||
func (bl *BitList) AddBit(bits ...bool) {
|
||||
for _, bit := range bits {
|
||||
itmIndex := bl.count / 32
|
||||
for itmIndex >= len(bl.data) {
|
||||
bl.grow()
|
||||
}
|
||||
bl.SetBit(bl.count, bit)
|
||||
bl.count++
|
||||
}
|
||||
}
|
||||
|
||||
// SetBit sets the bit at the given index to the given value
|
||||
func (bl *BitList) SetBit(index int, value bool) {
|
||||
itmIndex := index / 32
|
||||
itmBitShift := 31 - (index % 32)
|
||||
if value {
|
||||
bl.data[itmIndex] = bl.data[itmIndex] | 1<<uint(itmBitShift)
|
||||
} else {
|
||||
bl.data[itmIndex] = bl.data[itmIndex] & ^(1 << uint(itmBitShift))
|
||||
}
|
||||
}
|
||||
|
||||
// GetBit returns the bit at the given index
|
||||
func (bl *BitList) GetBit(index int) bool {
|
||||
itmIndex := index / 32
|
||||
itmBitShift := 31 - (index % 32)
|
||||
return ((bl.data[itmIndex] >> uint(itmBitShift)) & 1) == 1
|
||||
}
|
||||
|
||||
// AddByte appends all 8 bits of the given byte to the end of the list
|
||||
func (bl *BitList) AddByte(b byte) {
|
||||
for i := 7; i >= 0; i-- {
|
||||
bl.AddBit(((b >> uint(i)) & 1) == 1)
|
||||
}
|
||||
}
|
||||
|
||||
// AddBits appends the last (LSB) 'count' bits of 'b' the the end of the list
|
||||
func (bl *BitList) AddBits(b int, count byte) {
|
||||
for i := int(count) - 1; i >= 0; i-- {
|
||||
bl.AddBit(((b >> uint(i)) & 1) == 1)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBytes returns all bits of the BitList as a []byte
|
||||
func (bl *BitList) GetBytes() []byte {
|
||||
len := bl.count >> 3
|
||||
if (bl.count % 8) != 0 {
|
||||
len++
|
||||
}
|
||||
result := make([]byte, len)
|
||||
for i := 0; i < len; i++ {
|
||||
shift := (3 - (i % 4)) * 8
|
||||
result[i] = (byte)((bl.data[i/4] >> uint(shift)) & 0xFF)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IterateBytes iterates through all bytes contained in the BitList
|
||||
func (bl *BitList) IterateBytes() <-chan byte {
|
||||
res := make(chan byte)
|
||||
|
||||
go func() {
|
||||
c := bl.count
|
||||
shift := 24
|
||||
i := 0
|
||||
for c > 0 {
|
||||
res <- byte((bl.data[i] >> uint(shift)) & 0xFF)
|
||||
shift -= 8
|
||||
if shift < 0 {
|
||||
shift = 24
|
||||
i++
|
||||
}
|
||||
c -= 8
|
||||
}
|
||||
close(res)
|
||||
}()
|
||||
|
||||
return res
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package utils
|
||||
|
||||
// GaloisField encapsulates galois field arithmetics
|
||||
type GaloisField struct {
|
||||
Size int
|
||||
Base int
|
||||
ALogTbl []int
|
||||
LogTbl []int
|
||||
}
|
||||
|
||||
// NewGaloisField creates a new galois field
|
||||
func NewGaloisField(pp, fieldSize, b int) *GaloisField {
|
||||
result := new(GaloisField)
|
||||
|
||||
result.Size = fieldSize
|
||||
result.Base = b
|
||||
result.ALogTbl = make([]int, fieldSize)
|
||||
result.LogTbl = make([]int, fieldSize)
|
||||
|
||||
x := 1
|
||||
for i := 0; i < fieldSize; i++ {
|
||||
result.ALogTbl[i] = x
|
||||
x = x * 2
|
||||
if x >= fieldSize {
|
||||
x = (x ^ pp) & (fieldSize - 1)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < fieldSize; i++ {
|
||||
result.LogTbl[result.ALogTbl[i]] = int(i)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (gf *GaloisField) Zero() *GFPoly {
|
||||
return NewGFPoly(gf, []int{0})
|
||||
}
|
||||
|
||||
// AddOrSub add or substract two numbers
|
||||
func (gf *GaloisField) AddOrSub(a, b int) int {
|
||||
return a ^ b
|
||||
}
|
||||
|
||||
// Multiply multiplys two numbers
|
||||
func (gf *GaloisField) Multiply(a, b int) int {
|
||||
if a == 0 || b == 0 {
|
||||
return 0
|
||||
}
|
||||
return gf.ALogTbl[(gf.LogTbl[a]+gf.LogTbl[b])%(gf.Size-1)]
|
||||
}
|
||||
|
||||
// Divide divides two numbers
|
||||
func (gf *GaloisField) Divide(a, b int) int {
|
||||
if b == 0 {
|
||||
panic("divide by zero")
|
||||
} else if a == 0 {
|
||||
return 0
|
||||
}
|
||||
return gf.ALogTbl[(gf.LogTbl[a]-gf.LogTbl[b])%(gf.Size-1)]
|
||||
}
|
||||
|
||||
func (gf *GaloisField) Invers(num int) int {
|
||||
return gf.ALogTbl[(gf.Size-1)-gf.LogTbl[num]]
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package utils
|
||||
|
||||
type GFPoly struct {
|
||||
gf *GaloisField
|
||||
Coefficients []int
|
||||
}
|
||||
|
||||
func (gp *GFPoly) Degree() int {
|
||||
return len(gp.Coefficients) - 1
|
||||
}
|
||||
|
||||
func (gp *GFPoly) Zero() bool {
|
||||
return gp.Coefficients[0] == 0
|
||||
}
|
||||
|
||||
// GetCoefficient returns the coefficient of x ^ degree
|
||||
func (gp *GFPoly) GetCoefficient(degree int) int {
|
||||
return gp.Coefficients[gp.Degree()-degree]
|
||||
}
|
||||
|
||||
func (gp *GFPoly) AddOrSubstract(other *GFPoly) *GFPoly {
|
||||
if gp.Zero() {
|
||||
return other
|
||||
} else if other.Zero() {
|
||||
return gp
|
||||
}
|
||||
smallCoeff := gp.Coefficients
|
||||
largeCoeff := other.Coefficients
|
||||
if len(smallCoeff) > len(largeCoeff) {
|
||||
largeCoeff, smallCoeff = smallCoeff, largeCoeff
|
||||
}
|
||||
sumDiff := make([]int, len(largeCoeff))
|
||||
lenDiff := len(largeCoeff) - len(smallCoeff)
|
||||
copy(sumDiff, largeCoeff[:lenDiff])
|
||||
for i := lenDiff; i < len(largeCoeff); i++ {
|
||||
sumDiff[i] = int(gp.gf.AddOrSub(int(smallCoeff[i-lenDiff]), int(largeCoeff[i])))
|
||||
}
|
||||
return NewGFPoly(gp.gf, sumDiff)
|
||||
}
|
||||
|
||||
func (gp *GFPoly) MultByMonominal(degree int, coeff int) *GFPoly {
|
||||
if coeff == 0 {
|
||||
return gp.gf.Zero()
|
||||
}
|
||||
size := len(gp.Coefficients)
|
||||
result := make([]int, size+degree)
|
||||
for i := 0; i < size; i++ {
|
||||
result[i] = int(gp.gf.Multiply(int(gp.Coefficients[i]), int(coeff)))
|
||||
}
|
||||
return NewGFPoly(gp.gf, result)
|
||||
}
|
||||
|
||||
func (gp *GFPoly) Multiply(other *GFPoly) *GFPoly {
|
||||
if gp.Zero() || other.Zero() {
|
||||
return gp.gf.Zero()
|
||||
}
|
||||
aCoeff := gp.Coefficients
|
||||
aLen := len(aCoeff)
|
||||
bCoeff := other.Coefficients
|
||||
bLen := len(bCoeff)
|
||||
product := make([]int, aLen+bLen-1)
|
||||
for i := 0; i < aLen; i++ {
|
||||
ac := int(aCoeff[i])
|
||||
for j := 0; j < bLen; j++ {
|
||||
bc := int(bCoeff[j])
|
||||
product[i+j] = int(gp.gf.AddOrSub(int(product[i+j]), gp.gf.Multiply(ac, bc)))
|
||||
}
|
||||
}
|
||||
return NewGFPoly(gp.gf, product)
|
||||
}
|
||||
|
||||
func (gp *GFPoly) Divide(other *GFPoly) (quotient *GFPoly, remainder *GFPoly) {
|
||||
quotient = gp.gf.Zero()
|
||||
remainder = gp
|
||||
fld := gp.gf
|
||||
denomLeadTerm := other.GetCoefficient(other.Degree())
|
||||
inversDenomLeadTerm := fld.Invers(int(denomLeadTerm))
|
||||
for remainder.Degree() >= other.Degree() && !remainder.Zero() {
|
||||
degreeDiff := remainder.Degree() - other.Degree()
|
||||
scale := int(fld.Multiply(int(remainder.GetCoefficient(remainder.Degree())), inversDenomLeadTerm))
|
||||
term := other.MultByMonominal(degreeDiff, scale)
|
||||
itQuot := NewMonominalPoly(fld, degreeDiff, scale)
|
||||
quotient = quotient.AddOrSubstract(itQuot)
|
||||
remainder = remainder.AddOrSubstract(term)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewMonominalPoly(field *GaloisField, degree int, coeff int) *GFPoly {
|
||||
if coeff == 0 {
|
||||
return field.Zero()
|
||||
}
|
||||
result := make([]int, degree+1)
|
||||
result[0] = coeff
|
||||
return NewGFPoly(field, result)
|
||||
}
|
||||
|
||||
func NewGFPoly(field *GaloisField, coefficients []int) *GFPoly {
|
||||
for len(coefficients) > 1 && coefficients[0] == 0 {
|
||||
coefficients = coefficients[1:]
|
||||
}
|
||||
return &GFPoly{field, coefficients}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ReedSolomonEncoder struct {
|
||||
gf *GaloisField
|
||||
polynomes []*GFPoly
|
||||
m *sync.Mutex
|
||||
}
|
||||
|
||||
func NewReedSolomonEncoder(gf *GaloisField) *ReedSolomonEncoder {
|
||||
return &ReedSolomonEncoder{
|
||||
gf, []*GFPoly{NewGFPoly(gf, []int{1})}, new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *ReedSolomonEncoder) getPolynomial(degree int) *GFPoly {
|
||||
rs.m.Lock()
|
||||
defer rs.m.Unlock()
|
||||
|
||||
if degree >= len(rs.polynomes) {
|
||||
last := rs.polynomes[len(rs.polynomes)-1]
|
||||
for d := len(rs.polynomes); d <= degree; d++ {
|
||||
next := last.Multiply(NewGFPoly(rs.gf, []int{1, rs.gf.ALogTbl[d-1+rs.gf.Base]}))
|
||||
rs.polynomes = append(rs.polynomes, next)
|
||||
last = next
|
||||
}
|
||||
}
|
||||
return rs.polynomes[degree]
|
||||
}
|
||||
|
||||
func (rs *ReedSolomonEncoder) Encode(data []int, eccCount int) []int {
|
||||
generator := rs.getPolynomial(eccCount)
|
||||
info := NewGFPoly(rs.gf, data)
|
||||
info = info.MultByMonominal(eccCount, 1)
|
||||
_, remainder := info.Divide(generator)
|
||||
|
||||
result := make([]int, eccCount)
|
||||
numZero := int(eccCount) - len(remainder.Coefficients)
|
||||
copy(result[numZero:], remainder.Coefficients)
|
||||
return result
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package utils
|
||||
|
||||
// RuneToInt converts a rune between '0' and '9' to an integer between 0 and 9
|
||||
// If the rune is outside of this range -1 is returned.
|
||||
func RuneToInt(r rune) int {
|
||||
if r >= '0' && r <= '9' {
|
||||
return int(r - '0')
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// IntToRune converts a digit 0 - 9 to the rune '0' - '9'. If the given int is outside
|
||||
// of this range 'F' is returned!
|
||||
func IntToRune(i int) rune {
|
||||
if i >= 0 && i <= 9 {
|
||||
return rune(i + '0')
|
||||
}
|
||||
return 'F'
|
||||
}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
go test -bench=. -benchmem \
|
||||
goos: darwin \
|
||||
goarch: amd64 \
|
||||
pkg: github.com/brianvoe/gofakeit/v7 \
|
||||
cpu: Apple M1 Max \
|
||||
Table generated with tablesgenerator.com/markdown_tables File->Paste table data
|
||||
|
||||
| Benchmark | Ops | CPU | MEM | MEM alloc |
|
||||
|---------------------------------------|----------|----------------|--------------|------------------|
|
||||
| BenchmarkAddress-10 | 1369538 | 874.7 ns/op | 195 B/op | 5 allocs/op |
|
||||
| BenchmarkStreet-10 | 3438403 | 347.9 ns/op | 25 B/op | 2 allocs/op |
|
||||
| BenchmarkStreetNumber-10 | 8601847 | 138.2 ns/op | 4 B/op | 1 allocs/op |
|
||||
| BenchmarkStreetPrefix-10 | 19814623 | 60.26 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkStreetName-10 | 19633909 | 60.78 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkStreetSuffix-10 | 19717612 | 60.19 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCity-10 | 20219280 | 58.88 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkState-10 | 19526760 | 60.85 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkStateAbr-10 | 19634631 | 60.79 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkZip-10 | 7521580 | 157.7 ns/op | 5 B/op | 1 allocs/op |
|
||||
| BenchmarkCountry-10 | 19451166 | 61.29 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCountryAbr-10 | 19585867 | 60.82 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLatitude-10 | 72309668 | 16.22 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLongitude-10 | 72334910 | 16.23 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLatitudeInRange-10 | 65830375 | 17.77 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLongitudeInRange-10 | 66400602 | 17.77 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPetName-10 | 30391768 | 39.19 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAnimal-10 | 28761544 | 41.22 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAnimalType-10 | 26955640 | 44.13 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkFarmAnimal-10 | 22307872 | 53.39 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCat-10 | 24226416 | 49.13 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkDog-10 | 19702195 | 60.53 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBird-10 | 17095884 | 70.22 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAppName-10 | 3805696 | 314.4 ns/op | 25 B/op | 1 allocs/op |
|
||||
| BenchmarkAppVersion-10 | 10250247 | 116.4 ns/op | 7 B/op | 1 allocs/op |
|
||||
| BenchmarkAppAuthor-10 | 11592895 | 101.2 ns/op | 8 B/op | 0 allocs/op |
|
||||
| BenchmarkUsername-10 | 8975020 | 132.9 ns/op | 16 B/op | 2 allocs/op |
|
||||
| BenchmarkPassword-10 | 322147 | 3647 ns/op | 1656 B/op | 60 allocs/op |
|
||||
| BenchmarkBeerName-10 | 27986706 | 42.27 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBeerStyle-10 | 19460616 | 60.99 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBeerHop-10 | 26915132 | 44.35 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBeerYeast-10 | 24840991 | 47.98 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBeerMalt-10 | 20806075 | 57.18 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBeerIbu-10 | 41349307 | 28.99 ns/op | 8 B/op | 1 allocs/op |
|
||||
| BenchmarkBeerAlcohol-10 | 6054163 | 197.8 ns/op | 28 B/op | 2 allocs/op |
|
||||
| BenchmarkBeerBlg-10 | 5825622 | 205.6 ns/op | 37 B/op | 2 allocs/op |
|
||||
| BenchmarkBook-10 | 6927696 | 171.9 ns/op | 48 B/op | 1 allocs/op |
|
||||
| BenchmarkBookTitle-10 | 31594431 | 37.36 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBookAuthor-10 | 29969000 | 39.91 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBookGenre-10 | 24269676 | 48.77 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCar-10 | 3795943 | 316.3 ns/op | 96 B/op | 1 allocs/op |
|
||||
| BenchmarkCarType-10 | 26309082 | 43.81 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCarFuelType-10 | 26414821 | 45.18 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCarTransmissionType-10 | 24309171 | 48.83 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCarMaker-10 | 23505099 | 51.01 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCarModel-10 | 19055469 | 62.82 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCelebrityActor-10 | 19915483 | 57.84 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCelebrityBusiness-10 | 20186090 | 67.55 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCelebritySport-10 | 14223360 | 84.47 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkColor-10 | 21535978 | 54.16 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNiceColors-10 | 71414755 | 16.16 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkSafeColor-10 | 24683570 | 46.53 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHexColor-10 | 4815675 | 250.3 ns/op | 24 B/op | 3 allocs/op |
|
||||
| BenchmarkRGBColor-10 | 19453399 | 61.67 ns/op | 24 B/op | 1 allocs/op |
|
||||
| BenchmarkCompany-10 | 25604892 | 46.66 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCompanySuffix-10 | 24647574 | 48.83 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBlurb-10 | 20634126 | 58.88 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBuzzWord-10 | 23034157 | 51.84 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBS-10 | 21803314 | 55.08 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkJob-10 | 4121804 | 292.0 ns/op | 64 B/op | 1 allocs/op |
|
||||
| BenchmarkJobTitle-10 | 24344308 | 47.51 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkJobDescriptor-10 | 24049240 | 50.12 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkJobLevel-10 | 19349389 | 62.45 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkSlogan-10 | 4499653 | 263.1 ns/op | 41 B/op | 1 allocs/op |
|
||||
| BenchmarkCSVLookup100-10 | 1184 | 1014597 ns/op | 713620 B/op | 9923 allocs/op |
|
||||
| BenchmarkEmoji-10 | 24200866 | 49.72 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkEmojiDescription-10 | 22978600 | 52.18 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkEmojiCategory-10 | 21228057 | 56.57 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkEmojiAlias-10 | 17616240 | 68.45 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkEmojiTag-10 | 19253190 | 62.21 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkError-10 | 1637725 | 736.5 ns/op | 288 B/op | 8 allocs/op |
|
||||
| BenchmarkErrorObject-10 | 6755540 | 177.7 ns/op | 32 B/op | 3 allocs/op |
|
||||
| BenchmarkErrorDatabase-10 | 5794706 | 200.2 ns/op | 63 B/op | 3 allocs/op |
|
||||
| BenchmarkErrorGRPC-10 | 6063802 | 196.8 ns/op | 64 B/op | 3 allocs/op |
|
||||
| BenchmarkErrorHTTP-10 | 3956130 | 302.2 ns/op | 158 B/op | 4 allocs/op |
|
||||
| BenchmarkErrorHTTPClient-10 | 6025258 | 196.4 ns/op | 52 B/op | 3 allocs/op |
|
||||
| BenchmarkErrorHTTPServer-10 | 5969395 | 202.1 ns/op | 59 B/op | 3 allocs/op |
|
||||
| BenchmarkErrorRuntime-10 | 4786108 | 248.3 ns/op | 150 B/op | 3 allocs/op |
|
||||
| BenchmarkErrorValidation-10 | 1811821 | 667.8 ns/op | 277 B/op | 7 allocs/op |
|
||||
| BenchmarkFileMimeType-10 | 26273320 | 45.47 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkFileExtension-10 | 22216770 | 53.94 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCusip-10 | 6778542 | 176.4 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkIsin-10 | 1844566 | 652.1 ns/op | 525 B/op | 7 allocs/op |
|
||||
| BenchmarkFruit-10 | 20381516 | 58.81 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkVegetable-10 | 19638996 | 61.11 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBreakfast-10 | 9425649 | 127.2 ns/op | 32 B/op | 1 allocs/op |
|
||||
| BenchmarkLunch-10 | 8996594 | 133.6 ns/op | 34 B/op | 1 allocs/op |
|
||||
| BenchmarkDinner-10 | 9427389 | 126.6 ns/op | 36 B/op | 1 allocs/op |
|
||||
| BenchmarkDrink-10 | 8552294 | 140.4 ns/op | 7 B/op | 2 allocs/op |
|
||||
| BenchmarkSnack-10 | 7678719 | 156.7 ns/op | 32 B/op | 1 allocs/op |
|
||||
| BenchmarkDessert-10 | 8907098 | 134.0 ns/op | 31 B/op | 2 allocs/op |
|
||||
| BenchmarkGamertag-10 | 2474312 | 483.9 ns/op | 83 B/op | 5 allocs/op |
|
||||
| BenchmarkDice-10 | 47727080 | 25.22 ns/op | 8 B/op | 1 allocs/op |
|
||||
| BenchmarkGenerate/package-10 | 423741 | 2822 ns/op | 1187 B/op | 29 allocs/op |
|
||||
| BenchmarkGenerate/Complex-10 | 138112 | 8653 ns/op | 4553 B/op | 80 allocs/op |
|
||||
| BenchmarkFixedWidthLookup100-10 | 2072 | 583512 ns/op | 489975 B/op | 8701 allocs/op |
|
||||
| BenchmarkRegex-10 | 633699 | 1914 ns/op | 1632 B/op | 27 allocs/op |
|
||||
| BenchmarkRegexEmail-10 | 205447 | 5893 ns/op | 4084 B/op | 90 allocs/op |
|
||||
| BenchmarkMap-10 | 337576 | 3596 ns/op | 1111 B/op | 16 allocs/op |
|
||||
| BenchmarkHackerPhrase-10 | 166683 | 7209 ns/op | 3107 B/op | 50 allocs/op |
|
||||
| BenchmarkHackerAbbreviation-10 | 25295019 | 47.33 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHackerAdjective-10 | 24022460 | 49.76 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHackerNoun-10 | 22496308 | 53.31 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHackerVerb-10 | 18546052 | 64.53 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHackeringVerb-10 | 20298242 | 59.05 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkReplaceWithNumbers-10 | 1402717 | 852.8 ns/op | 296 B/op | 10 allocs/op |
|
||||
| BenchmarkHipsterWord-10 | 25151432 | 47.83 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHipsterSentence-10 | 1314279 | 907.8 ns/op | 288 B/op | 3 allocs/op |
|
||||
| BenchmarkHipsterParagraph-10 | 67437 | 17682 ns/op | 10521 B/op | 48 allocs/op |
|
||||
| BenchmarkInputName-10 | 20759898 | 57.98 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkSvg-10 | 225738 | 5181 ns/op | 8876 B/op | 52 allocs/op |
|
||||
| BenchmarkImageURL-10 | 15524359 | 77.15 ns/op | 38 B/op | 3 allocs/op |
|
||||
| BenchmarkImage-10 | 63 | 18773091 ns/op | 2457691 B/op | 307202 allocs/op |
|
||||
| BenchmarkImageJpeg-10 | 39 | 29498291 ns/op | 2982478 B/op | 307217 allocs/op |
|
||||
| BenchmarkImagePng-10 | 16 | 68552771 ns/op | 5899010 B/op | 307270 allocs/op |
|
||||
| BenchmarkDomainName-10 | 3001479 | 402.2 ns/op | 95 B/op | 5 allocs/op |
|
||||
| BenchmarkDomainSuffix-10 | 21476332 | 56.03 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkURL-10 | 1289262 | 934.6 ns/op | 277 B/op | 10 allocs/op |
|
||||
| BenchmarkHTTPMethod-10 | 19895946 | 60.56 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkIPv4Address-10 | 6088518 | 196.5 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkIPv6Address-10 | 2580320 | 462.0 ns/op | 111 B/op | 8 allocs/op |
|
||||
| BenchmarkMacAddress-10 | 3281300 | 364.7 ns/op | 24 B/op | 1 allocs/op |
|
||||
| BenchmarkHTTPStatusCode-10 | 16597051 | 72.18 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHTTPStatusCodeSimple-10 | 17250238 | 69.52 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLogLevel-10 | 20608036 | 58.20 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkUserAgent-10 | 1946059 | 615.5 ns/op | 298 B/op | 5 allocs/op |
|
||||
| BenchmarkChromeUserAgent-10 | 2619324 | 458.2 ns/op | 184 B/op | 5 allocs/op |
|
||||
| BenchmarkFirefoxUserAgent-10 | 1601706 | 753.8 ns/op | 362 B/op | 6 allocs/op |
|
||||
| BenchmarkSafariUserAgent-10 | 1569805 | 764.4 ns/op | 551 B/op | 7 allocs/op |
|
||||
| BenchmarkOperaUserAgent-10 | 2378972 | 504.7 ns/op | 212 B/op | 5 allocs/op |
|
||||
| BenchmarkJSONLookup100-10 | 928 | 1276230 ns/op | 813725 B/op | 12134 allocs/op |
|
||||
| BenchmarkLanguage-10 | 23873984 | 50.34 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLanguageAbbreviation-10 | 25025524 | 47.93 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLanguageBCP-10 | 21895112 | 54.74 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkProgrammingLanguage-10 | 21169636 | 56.70 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLoremIpsumWord-10 | 23980356 | 49.92 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLoremIpsumSentence-10 | 1344384 | 894.8 ns/op | 219 B/op | 2 allocs/op |
|
||||
| BenchmarkLoremIpsumParagraph-10 | 66643 | 17916 ns/op | 8483 B/op | 40 allocs/op |
|
||||
| BenchmarkMinecraftOre-10 | 15077451 | 79.85 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftWood-10 | 14422303 | 83.44 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftArmorTier-10 | 15262417 | 78.70 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftArmorPart-10 | 15340200 | 78.11 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftWeapon-10 | 15107792 | 79.78 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftTool-10 | 14428170 | 83.15 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftDye-10 | 14657188 | 81.95 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftFood-10 | 14860236 | 81.01 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftAnimal-10 | 15281302 | 78.35 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftVillagerJob-10 | 14586627 | 82.14 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftVillagerStation-10 | 14678592 | 81.82 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftVillagerLevel-10 | 14314164 | 83.76 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftMobPassive-10 | 15132750 | 79.32 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftMobNeutral-10 | 13802880 | 87.23 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftMobHostile-10 | 13141233 | 91.06 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftMobBoss-10 | 15245322 | 78.79 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftBiome-10 | 14943789 | 79.86 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinecraftWeather-10 | 12681386 | 94.55 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBool-10 | 73596490 | 16.18 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkUUID-10 | 4128735 | 288.7 ns/op | 48 B/op | 1 allocs/op |
|
||||
| BenchmarkShuffleAnySlice-10 | 3149857 | 380.0 ns/op | 24 B/op | 1 allocs/op |
|
||||
| BenchmarkFlipACoin-10 | 74457853 | 16.17 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMovie-10 | 9234234 | 129.3 ns/op | 32 B/op | 1 allocs/op |
|
||||
| BenchmarkMovieName-10 | 25314163 | 47.82 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMovieGenre-10 | 24570799 | 48.81 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNumber-10 | 74087221 | 16.21 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkUint8-10 | 73790145 | 16.35 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkUint16-10 | 74334474 | 16.27 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkUint32-10 | 71804154 | 16.72 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkUint64-10 | 71385904 | 16.64 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkUintRange-10 | 73982353 | 16.13 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkInt8-10 | 73927286 | 16.14 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkInt16-10 | 74022668 | 16.19 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkInt32-10 | 72009002 | 16.64 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkInt64-10 | 72375081 | 16.59 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkIntRange-10 | 74396306 | 16.20 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkFloat32-10 | 73950822 | 16.20 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkFloat32Range-10 | 73622833 | 16.18 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkFloat64-10 | 73076970 | 16.31 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkFloat64Range-10 | 73385329 | 16.33 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkShuffleInts-10 | 9151563 | 131.8 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkRandomInt-10 | 72188592 | 16.63 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkRandomUint-10 | 72293332 | 16.64 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHexUint-10 | 14888452 | 80.93 ns/op | 16 B/op | 2 allocs/op |
|
||||
| BenchmarkCurrency-10 | 14366668 | 83.15 ns/op | 32 B/op | 1 allocs/op |
|
||||
| BenchmarkCurrencyShort-10 | 24445954 | 48.68 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCurrencyLong-10 | 23560556 | 50.65 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPrice-10 | 73693664 | 16.33 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCreditCard-10 | 1000000 | 1153 ns/op | 264 B/op | 15 allocs/op |
|
||||
| BenchmarkCreditCardType-10 | 32410167 | 36.93 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkCreditCardNumber-10 | 1511084 | 799.1 ns/op | 183 B/op | 10 allocs/op |
|
||||
| BenchmarkCreditCardExp-10 | 11014600 | 108.5 ns/op | 5 B/op | 1 allocs/op |
|
||||
| BenchmarkCreditCardCvv-10 | 20325733 | 59.31 ns/op | 3 B/op | 1 allocs/op |
|
||||
| BenchmarkAchRouting-10 | 7338657 | 164.0 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkAchAccount-10 | 5646235 | 212.0 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkBitcoinAddress-10 | 517399 | 2306 ns/op | 715 B/op | 37 allocs/op |
|
||||
| BenchmarkBitcoinPrivateKey-10 | 1276884 | 943.2 ns/op | 184 B/op | 5 allocs/op |
|
||||
| BenchmarkName-10 | 7771977 | 152.6 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkFirstName-10 | 23523357 | 50.98 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMiddleName-10 | 17589612 | 68.17 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLastName-10 | 20825980 | 57.63 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNamePrefix-10 | 25542308 | 46.65 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNameSuffix-10 | 21972974 | 54.56 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkSSN-10 | 31829850 | 37.71 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkGender-10 | 73621140 | 16.25 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHobby-10 | 17347129 | 69.06 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPerson-10 | 317911 | 3693 ns/op | 837 B/op | 33 allocs/op |
|
||||
| BenchmarkContact-10 | 1843221 | 650.8 ns/op | 136 B/op | 6 allocs/op |
|
||||
| BenchmarkPhone-10 | 6786794 | 176.2 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkPhoneFormatted-10 | 4674930 | 256.2 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkEmail-10 | 2794358 | 431.1 ns/op | 88 B/op | 4 allocs/op |
|
||||
| BenchmarkTeams-10 | 1576238 | 763.8 ns/op | 672 B/op | 10 allocs/op |
|
||||
| BenchmarkProduct-10 | 206918 | 5813 ns/op | 1069 B/op | 31 allocs/op |
|
||||
| BenchmarkProductName-10 | 2313364 | 517.4 ns/op | 103 B/op | 5 allocs/op |
|
||||
| BenchmarkProductDescription-10 | 348346 | 3434 ns/op | 549 B/op | 8 allocs/op |
|
||||
| BenchmarkProductCategory-10 | 25139860 | 47.73 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkProductFeature-10 | 21264922 | 56.46 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkProductMaterial-10 | 18142828 | 66.24 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkProductUPC-10 | 1399148 | 859.1 ns/op | 96 B/op | 11 allocs/op |
|
||||
| BenchmarkSchool-10 | 4161710 | 287.6 ns/op | 34 B/op | 1 allocs/op |
|
||||
| BenchmarkLetter-10 | 73457020 | 16.29 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkLetterN-10 | 5060271 | 238.8 ns/op | 64 B/op | 2 allocs/op |
|
||||
| BenchmarkVowel-10 | 58685206 | 20.87 ns/op | 4 B/op | 1 allocs/op |
|
||||
| BenchmarkDigit-10 | 73944177 | 16.20 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkDigitN-10 | 5051070 | 236.6 ns/op | 64 B/op | 2 allocs/op |
|
||||
| BenchmarkNumerify-10 | 6794545 | 176.4 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkLexify-10 | 11113212 | 108.3 ns/op | 8 B/op | 1 allocs/op |
|
||||
| BenchmarkShuffleStrings-10 | 9924429 | 121.0 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkRandomString-10 | 73420688 | 16.34 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkTemplate-10 | 2488 | 477349 ns/op | 280877 B/op | 4611 allocs/op |
|
||||
| BenchmarkDate-10 | 10292476 | 116.2 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPastDate-10 | 18285830 | 65.48 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkFutureDate-10 | 18399240 | 65.13 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkDateRange-10 | 8406979 | 142.7 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMonth-10 | 74105902 | 16.26 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMonthString-10 | 73647870 | 16.26 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkWeekDay-10 | 73990911 | 16.24 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkDay-10 | 73435291 | 16.21 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkYear-10 | 73950066 | 16.21 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHour-10 | 74219916 | 16.21 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMinute-10 | 74349634 | 16.21 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkSecond-10 | 73787313 | 16.29 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNanoSecond-10 | 74299380 | 16.15 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkTimeZone-10 | 19105237 | 62.83 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkTimeZoneFull-10 | 16170054 | 74.27 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkTimeZoneAbv-10 | 20725029 | 58.23 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkTimeZoneOffset-10 | 14597666 | 81.13 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkTimeZoneRegion-10 | 15733551 | 76.25 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkWeighted-10 | 28507484 | 40.42 ns/op | 16 B/op | 1 allocs/op |
|
||||
| BenchmarkAdjective-10 | 6726474 | 178.3 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdjectiveDescriptive-10 | 16486224 | 73.39 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdjectiveQuantitative-10 | 15290762 | 78.51 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdjectiveProper-10 | 16535046 | 72.42 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdjectiveDemonstrative-10 | 16448917 | 73.41 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdjectivePossessive-10 | 15715839 | 73.22 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdjectiveInterrogative-10 | 15543478 | 77.43 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdjectiveIndefinite-10 | 16306894 | 73.50 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdverb-10 | 7139924 | 168.7 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdverbManner-10 | 17139112 | 70.37 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdverbDegree-10 | 16213138 | 73.70 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdverbPlace-10 | 17268267 | 69.67 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdverbTimeDefinite-10 | 16273309 | 73.70 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdverbTimeIndefinite-10 | 15822297 | 74.26 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdverbFrequencyDefinite-10 | 16344181 | 73.30 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAdverbFrequencyIndefinite-10 | 16118569 | 74.27 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkComment-10 | 1000000 | 1146 ns/op | 258 B/op | 6 allocs/op |
|
||||
| BenchmarkConnective-10 | 7132710 | 168.3 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkConnectiveTime-10 | 15339457 | 78.08 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkConnectiveComparative-10 | 16188842 | 74.04 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkConnectiveComplaint-10 | 14127903 | 85.00 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkConnectiveListing-10 | 16073437 | 74.65 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkConnectiveCasual-10 | 13771904 | 87.06 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkConnectiveExamplify-10 | 15763296 | 76.03 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkWord-10 | 8047610 | 148.5 ns/op | 3 B/op | 0 allocs/op |
|
||||
| BenchmarkSentenceSimple-10 | 682924 | 1707 ns/op | 590 B/op | 11 allocs/op |
|
||||
| BenchmarkInterjection-10 | 16295702 | 73.50 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNoun-10 | 6711976 | 179.3 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNounCommon-10 | 17117466 | 69.83 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNounConcrete-10 | 17144979 | 69.81 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNounAbstract-10 | 16839790 | 71.16 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNounCollectivePeople-10 | 16360652 | 73.24 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNounCollectiveAnimal-10 | 16453287 | 72.79 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNounCollectiveThing-10 | 16397232 | 72.97 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNounCountable-10 | 17171895 | 69.78 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNounUncountable-10 | 17193412 | 69.75 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkNounProper-10 | 10644372 | 112.0 ns/op | 7 B/op | 0 allocs/op |
|
||||
| BenchmarkNounDeterminer-10 | 17003730 | 70.44 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPhrase-10 | 23481584 | 51.12 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPhraseNoun-10 | 2961691 | 405.1 ns/op | 104 B/op | 2 allocs/op |
|
||||
| BenchmarkPhraseVerb-10 | 1422132 | 845.1 ns/op | 232 B/op | 6 allocs/op |
|
||||
| BenchmarkPhraseAdverb-10 | 7617193 | 153.3 ns/op | 9 B/op | 0 allocs/op |
|
||||
| BenchmarkPhrasePreposition-10 | 2336155 | 513.3 ns/op | 123 B/op | 3 allocs/op |
|
||||
| BenchmarkPreposition-10 | 9244665 | 129.9 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPrepositionSimple-10 | 16397623 | 73.11 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPrepositionDouble-10 | 16107751 | 74.19 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPrepositionCompound-10 | 16364900 | 73.10 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPronoun-10 | 6436707 | 186.4 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPronounPersonal-10 | 16997427 | 70.53 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPronounObject-10 | 15303380 | 78.27 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPronounPossessive-10 | 15323908 | 78.10 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPronounReflective-10 | 15258552 | 78.45 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPronounIndefinite-10 | 16053780 | 74.69 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPronounDemonstrative-10 | 16476726 | 72.73 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPronounInterrogative-10 | 15526576 | 77.15 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkPronounRelative-10 | 14159284 | 84.64 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkSentence-10 | 721934 | 1642 ns/op | 219 B/op | 3 allocs/op |
|
||||
| BenchmarkParagraph-10 | 39356 | 30481 ns/op | 6687 B/op | 53 allocs/op |
|
||||
| BenchmarkQuestion-10 | 1757269 | 683.1 ns/op | 243 B/op | 3 allocs/op |
|
||||
| BenchmarkQuote-10 | 1522988 | 787.2 ns/op | 261 B/op | 3 allocs/op |
|
||||
| BenchmarkVerb-10 | 8924802 | 127.6 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkVerbAction-10 | 17150564 | 69.83 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkVerbTransitive-10 | 17328488 | 69.21 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkVerbIntransitive-10 | 16427985 | 72.98 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkVerbLinking-10 | 17754280 | 67.52 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkVerbHelping-10 | 17118238 | 70.31 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkXMLLookup100-10 | 937 | 1279022 ns/op | 862536 B/op | 11370 allocs/op |
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at brian@webiswhatido.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
+1
@@ -0,0 +1 @@
|
||||
# Make a pull request and submit it and ill take a look at it. Thanks!
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) [year] [fullname]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+860
@@ -0,0 +1,860 @@
|
||||

|
||||
|
||||
# Gofakeit [](https://goreportcard.com/report/github.com/brianvoe/gofakeit)  [](https://godoc.org/github.com/brianvoe/gofakeit/v7) [](https://raw.githubusercontent.com/brianvoe/gofakeit/master/LICENSE.txt)
|
||||
|
||||
Random data generator written in go
|
||||
|
||||
[](https://ko-fi.com/G2G0R5EJT)
|
||||
|
||||
<a href="https://www.buymeacoffee.com/brianvoe" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
|
||||
|
||||
## Features
|
||||
|
||||
- [310+ Functions!!!](#functions)
|
||||
- [Random Sources](#random-sources)
|
||||
- [Global Rand](#global-rand-set)
|
||||
- [Struct Generator](#struct)
|
||||
- [Custom Functions](#custom-functions)
|
||||
- [Templates](#templates)
|
||||
- [Http Server](https://github.com/brianvoe/gofakeit/tree/master/cmd/gofakeitserver)
|
||||
- [Command Line Tool](https://github.com/brianvoe/gofakeit/tree/master/cmd/gofakeit)
|
||||
- Zero dependencies
|
||||
- [Benchmarks](https://github.com/brianvoe/gofakeit/blob/master/BENCHMARKS.md)
|
||||
- [Issue](https://github.com/brianvoe/gofakeit/issues)
|
||||
|
||||
## Contributors
|
||||
|
||||
Thank you to all our Gofakeit contributors!
|
||||
|
||||
<a href="https://github.com/brianvoe/gofakeit/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=brianvoe/gofakeit" />
|
||||
</a>
|
||||
|
||||
## Installation
|
||||
|
||||
```go
|
||||
go get github.com/brianvoe/gofakeit/v7
|
||||
```
|
||||
|
||||
## Simple Usage
|
||||
|
||||
```go
|
||||
import "github.com/brianvoe/gofakeit/v7"
|
||||
|
||||
gofakeit.Name() // Markus Moen
|
||||
gofakeit.Email() // alaynawuckert@kozey.biz
|
||||
gofakeit.Phone() // (570)245-7485
|
||||
gofakeit.BS() // front-end
|
||||
gofakeit.BeerName() // Duvel
|
||||
gofakeit.Color() // MediumOrchid
|
||||
gofakeit.Company() // Moen, Pagac and Wuckert
|
||||
gofakeit.CreditCardNumber() // 4287271570245748
|
||||
gofakeit.HackerPhrase() // Connecting the array won't do anything, we need to generate the haptic COM driver!
|
||||
gofakeit.JobTitle() // Director
|
||||
gofakeit.CurrencyShort() // USD
|
||||
```
|
||||
|
||||
[See full list of functions](#functions)
|
||||
|
||||
## Seed
|
||||
|
||||
If you are using the default global usage and dont care about seeding no need to set anything.
|
||||
Gofakeit will seed it with a cryptographically secure number.
|
||||
|
||||
If you need a reproducible outcome you can set it via the Seed function call. Every example in
|
||||
this repo sets it for testing purposes.
|
||||
|
||||
```go
|
||||
import "github.com/brianvoe/gofakeit/v7"
|
||||
|
||||
gofakeit.Seed(0) // If 0 will use crypto/rand to generate a number
|
||||
|
||||
// or
|
||||
|
||||
gofakeit.Seed(8675309) // Set it to whatever number you want
|
||||
```
|
||||
|
||||
## Random Sources
|
||||
|
||||
Gofakeit has a few rand sources, by default it uses math/rand/v2 PCG which is a pseudo random number generator and is thread locked.
|
||||
|
||||
If you want to see other potential sources you can see the sub package [Source](https://github.com/brianvoe/gofakeit/tree/master/source) for more information.
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/brianvoe/gofakeit/v7"
|
||||
"github.com/brianvoe/gofakeit/v7/source"
|
||||
"math/rand/v2"
|
||||
)
|
||||
|
||||
// Uses math/rand/v2(PCG Pseudo) with mutex locking
|
||||
faker := gofakeit.New(0)
|
||||
|
||||
// NewFaker takes in a source and whether or not it should be thread safe
|
||||
faker := gofakeit.NewFaker(source rand.Source, threadSafe bool)
|
||||
|
||||
// PCG Pseudo
|
||||
faker := gofakeit.NewFaker(rand.NewPCG(11, 11), true)
|
||||
|
||||
// ChaCha8
|
||||
faker := gofakeit.NewFaker(rand.NewChaCha8([32]byte{0, 1, 2, 3, 4, 5}), true)
|
||||
|
||||
|
||||
// Additional from Gofakeit sub package source
|
||||
|
||||
// JSF(Jenkins Small Fast)
|
||||
faker := gofakeit.NewFaker(source.NewJSF(11), true)
|
||||
|
||||
// SFC(Simple Fast Counter)
|
||||
faker := gofakeit.NewFaker(source.NewSFC(11), true)
|
||||
|
||||
// Crypto - Uses crypto/rand
|
||||
faker := gofakeit.NewFaker(source.NewCrypto(), true)
|
||||
|
||||
// Dumb - simple incrementing number
|
||||
faker := gofakeit.NewFaker(source.NewDumb(11), true)
|
||||
```
|
||||
|
||||
## Global Rand Set
|
||||
|
||||
If you would like to use the simple function calls but need to use something like
|
||||
crypto/rand you can override the default global with the random source that you want.
|
||||
|
||||
```go
|
||||
import "github.com/brianvoe/gofakeit/v7"
|
||||
|
||||
gofakeit.GlobalFaker = gofakeit.New(0)
|
||||
```
|
||||
|
||||
## Struct
|
||||
|
||||
Gofakeit can generate random data for struct fields. For the most part it covers all the basic type
|
||||
as well as some non-basic like time.Time.
|
||||
|
||||
Struct fields can also use tags to more specifically generate data for that field type.
|
||||
|
||||
```go
|
||||
import "github.com/brianvoe/gofakeit/v7"
|
||||
|
||||
// Create structs with random injected data
|
||||
type Foo struct {
|
||||
Str string
|
||||
Int int
|
||||
Pointer *int
|
||||
Name string `fake:"{firstname}"` // Any available function all lowercase
|
||||
Sentence string `fake:"{sentence:3}"` // Can call with parameters
|
||||
RandStr string `fake:"{randomstring:[hello,world]}"`
|
||||
Number string `fake:"{number:1,10}"` // Comma separated for multiple values
|
||||
Regex string `fake:"{regex:[abcdef]{5}}"` // Generate string from regex
|
||||
Map map[string]int `fakesize:"2"`
|
||||
Array []string `fakesize:"2"`
|
||||
ArrayRange []string `fakesize:"2,6"`
|
||||
Bar Bar
|
||||
Skip *string `fake:"skip"` // Set to "skip" to not generate data for
|
||||
SkipAlt *string `fake:"-"` // Set to "-" to not generate data for
|
||||
Created time.Time // Can take in a fake tag as well as a format tag
|
||||
CreatedFormat time.Time `fake:"{year}-{month}-{day}" format:"2006-01-02"`
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
Name string
|
||||
Number int
|
||||
Float float32
|
||||
}
|
||||
|
||||
// Pass your struct as a pointer
|
||||
var f Foo
|
||||
err := gofakeit.Struct(&f)
|
||||
|
||||
fmt.Println(f.Str) // hrukpttuezptneuvunh
|
||||
fmt.Println(f.Int) // -7825289004089916589
|
||||
fmt.Println(*f.Pointer) // -343806609094473732
|
||||
fmt.Println(f.Name) // fred
|
||||
fmt.Println(f.Sentence) // Record river mind.
|
||||
fmt.Println(fStr) // world
|
||||
fmt.Println(f.Number) // 4
|
||||
fmt.Println(f.Regex) // cbdfc
|
||||
fmt.Println(f.Map) // map[PxLIo:52 lxwnqhqc:846]
|
||||
fmt.Println(f.Array) // cbdfc
|
||||
fmt.Printf("%+v", f.Bar) // {Name:QFpZ Number:-2882647639396178786 Float:1.7636692e+37}
|
||||
fmt.Println(f.Skip) // <nil>
|
||||
fmt.Println(f.Created.String()) // 1908-12-07 04:14:25.685339029 +0000 UTC
|
||||
|
||||
// Supported formats
|
||||
// int, int8, int16, int32, int64,
|
||||
// uint, uint8, uint16, uint32, uint64,
|
||||
// float32, float64,
|
||||
// bool, string,
|
||||
// array, pointers, map
|
||||
// time.Time // If setting time you can also set a format tag
|
||||
// Nested Struct Fields and Embedded Fields
|
||||
```
|
||||
|
||||
## Fakeable types
|
||||
|
||||
It is possible to extend a struct by implementing the `Fakeable` interface
|
||||
in order to control the generation.
|
||||
|
||||
For example, this is useful when it is not possible to modify the struct that you want to fake by adding struct tags to a field but you still need to be able to control the generation process.
|
||||
|
||||
```go
|
||||
// Custom string that you want to generate your own data for
|
||||
type Friend string
|
||||
|
||||
func (c *Friend) Fake(f *gofakeit.Faker) (any, error) {
|
||||
// Can call any other faker methods
|
||||
return f.RandomString([]string{"billy", "fred", "susan"}), nil
|
||||
}
|
||||
|
||||
// Custom time that you want to generate your own data for
|
||||
type Age time.Time
|
||||
|
||||
func (c *Age) Fake(f *gofakeit.Faker) (any, error) {
|
||||
return f.DateRange(time.Now().AddDate(-100, 0, 0), time.Now().AddDate(-18, 0, 0)), nil
|
||||
}
|
||||
|
||||
// This is the struct that we cannot modify to add struct tags
|
||||
type User struct {
|
||||
Name Friend
|
||||
Age *Age
|
||||
}
|
||||
|
||||
var u User
|
||||
gofakeit.Struct(&u)
|
||||
fmt.Printf("%s", f.Name) // billy
|
||||
fmt.Printf("%s", f.Age) // 1990-12-07 04:14:25.685339029 +0000 UTC
|
||||
```
|
||||
|
||||
## Custom Functions
|
||||
|
||||
In a lot of situations you may need to use your own random function usage for your specific needs.
|
||||
|
||||
If you would like to extend the usage of struct tags, generate function, available usages in the gofakeit server
|
||||
or gofakeit command sub packages. You can do so via the AddFuncLookup. Each function has their own lookup, if
|
||||
you need more reference examples you can look at each files lookups.
|
||||
|
||||
```go
|
||||
// Simple
|
||||
gofakeit.AddFuncLookup("friendname", gofakeit.Info{
|
||||
Category: "custom",
|
||||
Description: "Random friend name",
|
||||
Example: "bill",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *gofakeit.MapParams, info *gofakeit.Info) (any, error) {
|
||||
return f.RandomString([]string{"bill", "bob", "sally"}), nil
|
||||
},
|
||||
})
|
||||
|
||||
// With Params
|
||||
gofakeit.AddFuncLookup("jumbleword", gofakeit.Info{
|
||||
Category: "jumbleword",
|
||||
Description: "Take a word and jumble it up",
|
||||
Example: "loredlowlh",
|
||||
Output: "string",
|
||||
Params: []gofakeit.Param{
|
||||
{Field: "word", Type: "string", Description: "Word you want to jumble"},
|
||||
},
|
||||
Generate: func(f *Faker, m *gofakeit.MapParams, info *gofakeit.Info) (any, error) {
|
||||
word, err := info.GetString(m, "word")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
split := strings.Split(word, "")
|
||||
f.ShuffleStrings(split)
|
||||
return strings.Join(split, ""), nil
|
||||
},
|
||||
})
|
||||
|
||||
type Foo struct {
|
||||
FriendName string `fake:"{friendname}"`
|
||||
JumbleWord string `fake:"{jumbleword:helloworld}"`
|
||||
}
|
||||
|
||||
var f Foo
|
||||
gofakeit.Struct(&f)
|
||||
fmt.Printf("%s", f.FriendName) // bill
|
||||
fmt.Printf("%s", f.JumbleWord) // loredlowlh
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
Generate custom outputs using golang's template engine [https://pkg.go.dev/text/template](https://pkg.go.dev/text/template).
|
||||
|
||||
We have added all the available functions to the template engine as well as some additional ones that are useful for template building.
|
||||
|
||||
Additional Available Functions
|
||||
```go
|
||||
- ToUpper(s string) string // Make string upper case
|
||||
- ToLower(s string) string // Make string lower case
|
||||
- ToString(s any) // Convert to string
|
||||
- ToDate(s string) time.Time // Convert string to date
|
||||
- SpliceAny(args ...any) []any // Build a slice of anys, used with Weighted
|
||||
- SpliceString(args ...string) []string // Build a slice of strings, used with Teams and RandomString
|
||||
- SpliceUInt(args ...uint) []uint // Build a slice of uint, used with Dice and RandomUint
|
||||
- SpliceInt(args ...int) []int // Build a slice of int, used with RandomInt
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Unavailable Gofakeit functions</summary>
|
||||
|
||||
```go
|
||||
// Any functions that dont have a return value
|
||||
- AnythingThatReturnsVoid(): void
|
||||
|
||||
// Not available to use in templates
|
||||
- Template(co *TemplateOptions) ([]byte, error)
|
||||
- RandomMapKey(mapI any) any
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
### Example Usages
|
||||
|
||||
```go
|
||||
import "github.com/brianvoe/gofakeit/v7"
|
||||
|
||||
func main() {
|
||||
// Accessing the Lines variable from within the template.
|
||||
template := `
|
||||
Subject: {{RandomString (SliceString "Greetings" "Hello" "Hi")}}
|
||||
|
||||
Dear {{LastName}},
|
||||
|
||||
{{RandomString (SliceString "Greetings!" "Hello there!" "Hi, how are you?")}}
|
||||
|
||||
{{Paragraph 1 5 10 "\n\n"}}
|
||||
|
||||
{{RandomString (SliceString "Warm regards" "Best wishes" "Sincerely")}}
|
||||
{{$person:=Person}}
|
||||
{{$person.FirstName}} {{$person.LastName}}
|
||||
{{$person.Email}}
|
||||
{{$person.Phone}}
|
||||
`
|
||||
|
||||
value, err := gofakeit.Template(template, &TemplateOptions{Data: 5})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(value))
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```text
|
||||
Subject: Hello
|
||||
|
||||
Dear Krajcik,
|
||||
|
||||
Greetings!
|
||||
|
||||
Quia voluptatem voluptatem voluptatem. Quia voluptatem voluptatem voluptatem. Quia voluptatem voluptatem voluptatem.
|
||||
|
||||
Warm regards
|
||||
Kaitlyn Krajcik
|
||||
kaitlynkrajcik@krajcik
|
||||
570-245-7485
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
All functions also exist as methods on the Faker struct
|
||||
|
||||
### File
|
||||
|
||||
Passing `nil` to `CSV`, `JSON` or `XML` will auto generate data using default values.
|
||||
|
||||
```go
|
||||
CSV(co *CSVOptions) ([]byte, error)
|
||||
JSON(jo *JSONOptions) ([]byte, error)
|
||||
XML(xo *XMLOptions) ([]byte, error)
|
||||
FileExtension() string
|
||||
FileMimeType() string
|
||||
```
|
||||
|
||||
### Template
|
||||
|
||||
Passing `nil` will auto generate data using default values.
|
||||
|
||||
```go
|
||||
Template(co *TemplateOptions) (string, error)
|
||||
Markdown(co *MarkdownOptions) (string, error)
|
||||
EmailText(co *EmailOptions) (string, error)
|
||||
FixedWidth(co *FixedWidthOptions) (string, error)
|
||||
```
|
||||
|
||||
### Product
|
||||
|
||||
```go
|
||||
Product() *ProductInfo
|
||||
ProductName() string
|
||||
ProductDescription() string
|
||||
ProductCategory() string
|
||||
ProductFeature() string
|
||||
ProductMaterial() string
|
||||
```
|
||||
|
||||
### Person
|
||||
|
||||
```go
|
||||
Person() *PersonInfo
|
||||
Name() string
|
||||
NamePrefix() string
|
||||
NameSuffix() string
|
||||
FirstName() string
|
||||
MiddleName() string
|
||||
LastName() string
|
||||
Gender() string
|
||||
SSN() string
|
||||
Hobby() string
|
||||
Contact() *ContactInfo
|
||||
Email() string
|
||||
Phone() string
|
||||
PhoneFormatted() string
|
||||
Teams(peopleArray []string, teamsArray []string) map[string][]string
|
||||
```
|
||||
|
||||
### Generate
|
||||
|
||||
```go
|
||||
Struct(v any)
|
||||
Slice(v any)
|
||||
Map() map[string]any
|
||||
Generate(value string) string
|
||||
Regex(value string) string
|
||||
```
|
||||
|
||||
### Auth
|
||||
|
||||
```go
|
||||
Username() string
|
||||
Password(lower bool, upper bool, numeric bool, special bool, space bool, num int) string
|
||||
```
|
||||
|
||||
### Address
|
||||
|
||||
```go
|
||||
Address() *AddressInfo
|
||||
City() string
|
||||
Country() string
|
||||
CountryAbr() string
|
||||
State() string
|
||||
StateAbr() string
|
||||
Street() string
|
||||
StreetName() string
|
||||
StreetNumber() string
|
||||
StreetPrefix() string
|
||||
StreetSuffix() string
|
||||
Zip() string
|
||||
Latitude() float64
|
||||
LatitudeInRange(min, max float64) (float64, error)
|
||||
Longitude() float64
|
||||
LongitudeInRange(min, max float64) (float64, error)
|
||||
```
|
||||
|
||||
### Game
|
||||
|
||||
```go
|
||||
Gamertag() string
|
||||
Dice(numDice uint, sides []uint) []uint
|
||||
```
|
||||
|
||||
### Beer
|
||||
|
||||
```go
|
||||
BeerAlcohol() string
|
||||
BeerBlg() string
|
||||
BeerHop() string
|
||||
BeerIbu() string
|
||||
BeerMalt() string
|
||||
BeerName() string
|
||||
BeerStyle() string
|
||||
BeerYeast() string
|
||||
```
|
||||
|
||||
### Car
|
||||
|
||||
```go
|
||||
Car() *CarInfo
|
||||
CarMaker() string
|
||||
CarModel() string
|
||||
CarType() string
|
||||
CarFuelType() string
|
||||
CarTransmissionType() string
|
||||
```
|
||||
|
||||
### Words
|
||||
|
||||
```go
|
||||
// Nouns
|
||||
Noun() string
|
||||
NounCommon() string
|
||||
NounConcrete() string
|
||||
NounAbstract() string
|
||||
NounCollectivePeople() string
|
||||
NounCollectiveAnimal() string
|
||||
NounCollectiveThing() string
|
||||
NounCountable() string
|
||||
NounUncountable() string
|
||||
|
||||
// Verbs
|
||||
Verb() string
|
||||
VerbAction() string
|
||||
VerbLinking() string
|
||||
VerbHelping() string
|
||||
|
||||
// Adverbs
|
||||
Adverb() string
|
||||
AdverbManner() string
|
||||
AdverbDegree() string
|
||||
AdverbPlace() string
|
||||
AdverbTimeDefinite() string
|
||||
AdverbTimeIndefinite() string
|
||||
AdverbFrequencyDefinite() string
|
||||
AdverbFrequencyIndefinite() string
|
||||
|
||||
// Propositions
|
||||
Preposition() string
|
||||
PrepositionSimple() string
|
||||
PrepositionDouble() string
|
||||
PrepositionCompound() string
|
||||
|
||||
// Adjectives
|
||||
Adjective() string
|
||||
AdjectiveDescriptive() string
|
||||
AdjectiveQuantitative() string
|
||||
AdjectiveProper() string
|
||||
AdjectiveDemonstrative() string
|
||||
AdjectivePossessive() string
|
||||
AdjectiveInterrogative() string
|
||||
AdjectiveIndefinite() string
|
||||
|
||||
// Pronouns
|
||||
Pronoun() string
|
||||
PronounPersonal() string
|
||||
PronounObject() string
|
||||
PronounPossessive() string
|
||||
PronounReflective() string
|
||||
PronounDemonstrative() string
|
||||
PronounInterrogative() string
|
||||
PronounRelative() string
|
||||
|
||||
// Connectives
|
||||
Connective() string
|
||||
ConnectiveTime() string
|
||||
ConnectiveComparative() string
|
||||
ConnectiveComplaint() string
|
||||
ConnectiveListing() string
|
||||
ConnectiveCasual() string
|
||||
ConnectiveExamplify() string
|
||||
|
||||
// Words
|
||||
Word() string
|
||||
|
||||
// Sentences
|
||||
Sentence(wordCount int) string
|
||||
Paragraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string
|
||||
LoremIpsumWord() string
|
||||
LoremIpsumSentence(wordCount int) string
|
||||
LoremIpsumParagraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string
|
||||
Question() string
|
||||
Quote() string
|
||||
Phrase() string
|
||||
```
|
||||
|
||||
### Foods
|
||||
|
||||
```go
|
||||
Fruit() string
|
||||
Vegetable() string
|
||||
Breakfast() string
|
||||
Lunch() string
|
||||
Dinner() string
|
||||
Snack() string
|
||||
Dessert() string
|
||||
```
|
||||
|
||||
### Misc
|
||||
|
||||
```go
|
||||
Bool() bool
|
||||
UUID() string
|
||||
Weighted(options []any, weights []float32) (any, error)
|
||||
FlipACoin() string
|
||||
RandomMapKey(mapI any) any
|
||||
ShuffleAnySlice(v any)
|
||||
```
|
||||
|
||||
### Colors
|
||||
|
||||
```go
|
||||
Color() string
|
||||
HexColor() string
|
||||
RGBColor() []int
|
||||
SafeColor() string
|
||||
NiceColors() string
|
||||
```
|
||||
|
||||
### Images
|
||||
|
||||
```go
|
||||
Image(width int, height int) *img.RGBA
|
||||
ImageJpeg(width int, height int) []byte
|
||||
ImagePng(width int, height int) []byte
|
||||
```
|
||||
|
||||
### Internet
|
||||
|
||||
```go
|
||||
URL() string
|
||||
DomainName() string
|
||||
DomainSuffix() string
|
||||
IPv4Address() string
|
||||
IPv6Address() string
|
||||
MacAddress() string
|
||||
HTTPStatusCode() string
|
||||
HTTPStatusCodeSimple() int
|
||||
LogLevel(logType string) string
|
||||
HTTPMethod() string
|
||||
HTTPVersion() string
|
||||
UserAgent() string
|
||||
ChromeUserAgent() string
|
||||
FirefoxUserAgent() string
|
||||
OperaUserAgent() string
|
||||
SafariUserAgent() string
|
||||
```
|
||||
|
||||
### HTML
|
||||
|
||||
```go
|
||||
InputName() string
|
||||
Svg(options *SVGOptions) string
|
||||
```
|
||||
|
||||
### Date/Time
|
||||
|
||||
```go
|
||||
Date() time.Time
|
||||
PastDate() time.Time
|
||||
FutureDate() time.Time
|
||||
DateRange(start, end time.Time) time.Time
|
||||
NanoSecond() int
|
||||
Second() int
|
||||
Minute() int
|
||||
Hour() int
|
||||
Month() int
|
||||
MonthString() string
|
||||
Day() int
|
||||
WeekDay() string
|
||||
Year() int
|
||||
TimeZone() string
|
||||
TimeZoneAbv() string
|
||||
TimeZoneFull() string
|
||||
TimeZoneOffset() float32
|
||||
TimeZoneRegion() string
|
||||
```
|
||||
|
||||
### Payment
|
||||
|
||||
```go
|
||||
Price(min, max float64) float64
|
||||
CreditCard() *CreditCardInfo
|
||||
CreditCardCvv() string
|
||||
CreditCardExp() string
|
||||
CreditCardNumber(*CreditCardOptions) string
|
||||
CreditCardType() string
|
||||
Currency() *CurrencyInfo
|
||||
CurrencyLong() string
|
||||
CurrencyShort() string
|
||||
AchRouting() string
|
||||
AchAccount() string
|
||||
BitcoinAddress() string
|
||||
BitcoinPrivateKey() string
|
||||
```
|
||||
|
||||
### Finance
|
||||
|
||||
```go
|
||||
Cusip() string
|
||||
Isin() string
|
||||
```
|
||||
|
||||
### Company
|
||||
|
||||
```go
|
||||
BS() string
|
||||
Blurb() string
|
||||
BuzzWord() string
|
||||
Company() string
|
||||
CompanySuffix() string
|
||||
Job() *JobInfo
|
||||
JobDescriptor() string
|
||||
JobLevel() string
|
||||
JobTitle() string
|
||||
Slogan() string
|
||||
```
|
||||
|
||||
### Hacker
|
||||
|
||||
```go
|
||||
HackerAbbreviation() string
|
||||
HackerAdjective() string
|
||||
Hackeringverb() string
|
||||
HackerNoun() string
|
||||
HackerPhrase() string
|
||||
HackerVerb() string
|
||||
```
|
||||
|
||||
### Hipster
|
||||
|
||||
```go
|
||||
HipsterWord() string
|
||||
HipsterSentence(wordCount int) string
|
||||
HipsterParagraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string
|
||||
```
|
||||
|
||||
### App
|
||||
|
||||
```go
|
||||
AppName() string
|
||||
AppVersion() string
|
||||
AppAuthor() string
|
||||
```
|
||||
|
||||
### Animal
|
||||
|
||||
```go
|
||||
PetName() string
|
||||
Animal() string
|
||||
AnimalType() string
|
||||
FarmAnimal() string
|
||||
Cat() string
|
||||
Dog() string
|
||||
Bird() string
|
||||
```
|
||||
|
||||
### Emoji
|
||||
|
||||
```go
|
||||
Emoji() string
|
||||
EmojiDescription() string
|
||||
EmojiCategory() string
|
||||
EmojiAlias() string
|
||||
EmojiTag() string
|
||||
```
|
||||
|
||||
### Language
|
||||
|
||||
```go
|
||||
Language() string
|
||||
LanguageAbbreviation() string
|
||||
ProgrammingLanguage() string
|
||||
ProgrammingLanguageBest() string
|
||||
```
|
||||
|
||||
### Number
|
||||
|
||||
```go
|
||||
Number(min int, max int) int
|
||||
Int() int
|
||||
IntN(n int) int
|
||||
Int8() int8
|
||||
Int16() int16
|
||||
Int32() int32
|
||||
Int64() int64
|
||||
Uint() uint
|
||||
UintN(n uint) uint
|
||||
Uint8() uint8
|
||||
Uint16() uint16
|
||||
Uint32() uint32
|
||||
Uint64() uint64
|
||||
Float32() float32
|
||||
Float32Range(min, max float32) float32
|
||||
Float64() float64
|
||||
Float64Range(min, max float64) float64
|
||||
ShuffleInts(a []int)
|
||||
RandomInt(i []int) int
|
||||
HexUint(bitsize int) string
|
||||
```
|
||||
|
||||
### String
|
||||
|
||||
```go
|
||||
Digit() string
|
||||
DigitN(n uint) string
|
||||
Letter() string
|
||||
LetterN(n uint) string
|
||||
Lexify(str string) string
|
||||
Numerify(str string) string
|
||||
ShuffleStrings(a []string)
|
||||
RandomString(a []string) string
|
||||
```
|
||||
|
||||
### Celebrity
|
||||
|
||||
```go
|
||||
CelebrityActor() string
|
||||
CelebrityBusiness() string
|
||||
CelebritySport() string
|
||||
```
|
||||
|
||||
### Minecraft
|
||||
|
||||
```go
|
||||
MinecraftOre() string
|
||||
MinecraftWood() string
|
||||
MinecraftArmorTier() string
|
||||
MinecraftArmorPart() string
|
||||
MinecraftWeapon() string
|
||||
MinecraftTool() string
|
||||
MinecraftDye() string
|
||||
MinecraftFood() string
|
||||
MinecraftAnimal() string
|
||||
MinecraftVillagerJob() string
|
||||
MinecraftVillagerStation() string
|
||||
MinecraftVillagerLevel() string
|
||||
MinecraftMobPassive() string
|
||||
MinecraftMobNeutral() string
|
||||
MinecraftMobHostile() string
|
||||
MinecraftMobBoss() string
|
||||
MinecraftBiome() string
|
||||
MinecraftWeather() string
|
||||
```
|
||||
|
||||
### Book
|
||||
|
||||
```go
|
||||
Book() *BookInfo
|
||||
BookTitle() string
|
||||
BookAuthor() string
|
||||
BookGenre() string
|
||||
```
|
||||
|
||||
### Movie
|
||||
|
||||
```go
|
||||
Movie() *MovieInfo
|
||||
MovieName() string
|
||||
MovieGenre() string
|
||||
```
|
||||
|
||||
### Error
|
||||
|
||||
```go
|
||||
Error() error
|
||||
ErrorDatabase() error
|
||||
ErrorGRPC() error
|
||||
ErrorHTTP() error
|
||||
ErrorHTTPClient() error
|
||||
ErrorHTTPServer() error
|
||||
ErrorInput() error
|
||||
ErrorRuntime() error
|
||||
```
|
||||
|
||||
### School
|
||||
|
||||
```go
|
||||
School() string
|
||||
```
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
package gofakeit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AddressInfo is a struct full of address information
|
||||
type AddressInfo struct {
|
||||
Address string `json:"address" xml:"address"`
|
||||
Street string `json:"street" xml:"street"`
|
||||
City string `json:"city" xml:"city"`
|
||||
State string `json:"state" xml:"state"`
|
||||
Zip string `json:"zip" xml:"zip"`
|
||||
Country string `json:"country" xml:"country"`
|
||||
Latitude float64 `json:"latitude" xml:"latitude"`
|
||||
Longitude float64 `json:"longitude" xml:"longitude"`
|
||||
}
|
||||
|
||||
// Address will generate a struct of address information
|
||||
func Address() *AddressInfo { return address(GlobalFaker) }
|
||||
|
||||
// Address will generate a struct of address information
|
||||
func (f *Faker) Address() *AddressInfo { return address(f) }
|
||||
|
||||
func address(f *Faker) *AddressInfo {
|
||||
street := street(f)
|
||||
city := city(f)
|
||||
state := state(f)
|
||||
zip := zip(f)
|
||||
|
||||
return &AddressInfo{
|
||||
Address: street + ", " + city + ", " + state + " " + zip,
|
||||
Street: street,
|
||||
City: city,
|
||||
State: state,
|
||||
Zip: zip,
|
||||
Country: country(f),
|
||||
Latitude: latitude(f),
|
||||
Longitude: longitude(f),
|
||||
}
|
||||
}
|
||||
|
||||
// Street will generate a random address street string
|
||||
func Street() string { return street(GlobalFaker) }
|
||||
|
||||
// Street will generate a random address street string
|
||||
func (f *Faker) Street() string { return street(f) }
|
||||
|
||||
func street(f *Faker) string {
|
||||
var street = ""
|
||||
switch randInt := randIntRange(f, 1, 2); randInt {
|
||||
case 1:
|
||||
street = streetNumber(f) + " " + streetPrefix(f) + " " + streetName(f) + streetSuffix(f)
|
||||
case 2:
|
||||
street = streetNumber(f) + " " + streetName(f) + streetSuffix(f)
|
||||
}
|
||||
|
||||
return street
|
||||
}
|
||||
|
||||
// StreetNumber will generate a random address street number string
|
||||
func StreetNumber() string { return streetNumber(GlobalFaker) }
|
||||
|
||||
// StreetNumber will generate a random address street number string
|
||||
func (f *Faker) StreetNumber() string { return streetNumber(f) }
|
||||
|
||||
func streetNumber(f *Faker) string {
|
||||
return strings.TrimLeft(replaceWithNumbers(f, getRandValue(f, []string{"address", "number"})), "0")
|
||||
}
|
||||
|
||||
// StreetPrefix will generate a random address street prefix string
|
||||
func StreetPrefix() string { return streetPrefix(GlobalFaker) }
|
||||
|
||||
// StreetPrefix will generate a random address street prefix string
|
||||
func (f *Faker) StreetPrefix() string { return streetPrefix(f) }
|
||||
|
||||
func streetPrefix(f *Faker) string { return getRandValue(f, []string{"address", "street_prefix"}) }
|
||||
|
||||
// StreetName will generate a random address street name string
|
||||
func StreetName() string { return streetName(GlobalFaker) }
|
||||
|
||||
// StreetName will generate a random address street name string
|
||||
func (f *Faker) StreetName() string { return streetName(f) }
|
||||
|
||||
func streetName(f *Faker) string { return getRandValue(f, []string{"address", "street_name"}) }
|
||||
|
||||
// StreetSuffix will generate a random address street suffix string
|
||||
func StreetSuffix() string { return streetSuffix(GlobalFaker) }
|
||||
|
||||
// StreetSuffix will generate a random address street suffix string
|
||||
func (f *Faker) StreetSuffix() string { return streetSuffix(f) }
|
||||
|
||||
func streetSuffix(f *Faker) string { return getRandValue(f, []string{"address", "street_suffix"}) }
|
||||
|
||||
// City will generate a random city string
|
||||
func City() string { return city(GlobalFaker) }
|
||||
|
||||
// City will generate a random city string
|
||||
func (f *Faker) City() string { return city(f) }
|
||||
|
||||
func city(f *Faker) string { return getRandValue(f, []string{"address", "city"}) }
|
||||
|
||||
// State will generate a random state string
|
||||
func State() string { return state(GlobalFaker) }
|
||||
|
||||
// State will generate a random state string
|
||||
func (f *Faker) State() string { return state(f) }
|
||||
|
||||
func state(f *Faker) string { return getRandValue(f, []string{"address", "state"}) }
|
||||
|
||||
// StateAbr will generate a random abbreviated state string
|
||||
func StateAbr() string { return stateAbr(GlobalFaker) }
|
||||
|
||||
// StateAbr will generate a random abbreviated state string
|
||||
func (f *Faker) StateAbr() string { return stateAbr(f) }
|
||||
|
||||
func stateAbr(f *Faker) string { return getRandValue(f, []string{"address", "state_abr"}) }
|
||||
|
||||
// Zip will generate a random Zip code string
|
||||
func Zip() string { return zip(GlobalFaker) }
|
||||
|
||||
// Zip will generate a random Zip code string
|
||||
func (f *Faker) Zip() string { return zip(f) }
|
||||
|
||||
func zip(f *Faker) string {
|
||||
return replaceWithNumbers(f, getRandValue(f, []string{"address", "zip"}))
|
||||
}
|
||||
|
||||
// Country will generate a random country string
|
||||
func Country() string { return country(GlobalFaker) }
|
||||
|
||||
// Country will generate a random country string
|
||||
func (f *Faker) Country() string { return country(f) }
|
||||
|
||||
func country(f *Faker) string { return getRandValue(f, []string{"address", "country"}) }
|
||||
|
||||
// CountryAbr will generate a random abbreviated country string
|
||||
func CountryAbr() string { return countryAbr(GlobalFaker) }
|
||||
|
||||
// CountryAbr will generate a random abbreviated country string
|
||||
func (f *Faker) CountryAbr() string { return countryAbr(f) }
|
||||
|
||||
func countryAbr(f *Faker) string { return getRandValue(f, []string{"address", "country_abr"}) }
|
||||
|
||||
// Latitude will generate a random latitude float64
|
||||
func Latitude() float64 { return latitude(GlobalFaker) }
|
||||
|
||||
// Latitude will generate a random latitude float64
|
||||
func (f *Faker) Latitude() float64 { return latitude(f) }
|
||||
|
||||
func latitude(f *Faker) float64 { return toFixed((f.Float64()*180)-90, 6) }
|
||||
|
||||
// LatitudeInRange will generate a random latitude within the input range
|
||||
func LatitudeInRange(min, max float64) (float64, error) {
|
||||
return latitudeInRange(GlobalFaker, min, max)
|
||||
}
|
||||
|
||||
// LatitudeInRange will generate a random latitude within the input range
|
||||
func (f *Faker) LatitudeInRange(min, max float64) (float64, error) {
|
||||
return latitudeInRange(f, min, max)
|
||||
}
|
||||
|
||||
func latitudeInRange(f *Faker, min, max float64) (float64, error) {
|
||||
if min > max || min < -90 || min > 90 || max < -90 || max > 90 {
|
||||
return 0, errors.New("invalid min or max range, must be valid floats and between -90 and 90")
|
||||
}
|
||||
return toFixed(float64Range(f, min, max), 6), nil
|
||||
}
|
||||
|
||||
// Longitude will generate a random longitude float64
|
||||
func Longitude() float64 { return longitude(GlobalFaker) }
|
||||
|
||||
// Longitude will generate a random longitude float64
|
||||
func (f *Faker) Longitude() float64 { return longitude(f) }
|
||||
|
||||
func longitude(f *Faker) float64 { return toFixed((f.Float64()*360)-180, 6) }
|
||||
|
||||
// LongitudeInRange will generate a random longitude within the input range
|
||||
func LongitudeInRange(min, max float64) (float64, error) {
|
||||
return longitudeInRange(GlobalFaker, min, max)
|
||||
}
|
||||
|
||||
// LongitudeInRange will generate a random longitude within the input range
|
||||
func (f *Faker) LongitudeInRange(min, max float64) (float64, error) {
|
||||
return longitudeInRange(f, min, max)
|
||||
}
|
||||
|
||||
func longitudeInRange(f *Faker, min, max float64) (float64, error) {
|
||||
if min > max || min < -180 || min > 180 || max < -180 || max > 180 {
|
||||
return 0, errors.New("invalid min or max range, must be valid floats and between -180 and 180")
|
||||
}
|
||||
return toFixed(float64Range(f, min, max), 6), nil
|
||||
}
|
||||
|
||||
func addAddressLookup() {
|
||||
AddFuncLookup("address", Info{
|
||||
Display: "Address",
|
||||
Category: "address",
|
||||
Description: "Residential location including street, city, state, country and postal code",
|
||||
Example: `{
|
||||
"address": "364 Unionsville, Norfolk, Ohio 99536",
|
||||
"street": "364 Unionsville",
|
||||
"city": "Norfolk",
|
||||
"state": "Ohio",
|
||||
"zip": "99536",
|
||||
"country": "Lesotho",
|
||||
"latitude": 88.792592,
|
||||
"longitude": 174.504681
|
||||
}`,
|
||||
Output: "map[string]any",
|
||||
ContentType: "application/json",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return address(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("city", Info{
|
||||
Display: "City",
|
||||
Category: "address",
|
||||
Description: "Part of a country with significant population, often a central hub for culture and commerce",
|
||||
Example: "Marcelside",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return city(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("country", Info{
|
||||
Display: "Country",
|
||||
Category: "address",
|
||||
Description: "Nation with its own government and defined territory",
|
||||
Example: "United States of America",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return country(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("countryabr", Info{
|
||||
Display: "Country Abbreviation",
|
||||
Category: "address",
|
||||
Description: "Shortened 2-letter form of a country's name",
|
||||
Example: "US",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return countryAbr(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("state", Info{
|
||||
Display: "State",
|
||||
Category: "address",
|
||||
Description: "Governmental division within a country, often having its own laws and government",
|
||||
Example: "Illinois",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return state(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("stateabr", Info{
|
||||
Display: "State Abbreviation",
|
||||
Category: "address",
|
||||
Description: "Shortened 2-letter form of a country's state",
|
||||
Example: "IL",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return stateAbr(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("street", Info{
|
||||
Display: "Street",
|
||||
Category: "address",
|
||||
Description: "Public road in a city or town, typically with houses and buildings on each side",
|
||||
Example: "364 East Rapidsborough",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return street(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("streetname", Info{
|
||||
Display: "Street Name",
|
||||
Category: "address",
|
||||
Description: "Name given to a specific road or street",
|
||||
Example: "View",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return streetName(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("streetnumber", Info{
|
||||
Display: "Street Number",
|
||||
Category: "address",
|
||||
Description: "Numerical identifier assigned to a street",
|
||||
Example: "13645",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return streetNumber(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("streetprefix", Info{
|
||||
Display: "Street Prefix",
|
||||
Category: "address",
|
||||
Description: "Directional or descriptive term preceding a street name, like 'East' or 'Main'",
|
||||
Example: "Lake",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return streetPrefix(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("streetsuffix", Info{
|
||||
Display: "Street Suffix",
|
||||
Category: "address",
|
||||
Description: "Designation at the end of a street name indicating type, like 'Avenue' or 'Street'",
|
||||
Example: "land",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return streetSuffix(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("zip", Info{
|
||||
Display: "Zip",
|
||||
Category: "address",
|
||||
Description: "Numerical code for postal address sorting, specific to a geographic area",
|
||||
Example: "13645",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return zip(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("latitude", Info{
|
||||
Display: "Latitude",
|
||||
Category: "address",
|
||||
Description: "Geographic coordinate specifying north-south position on Earth's surface",
|
||||
Example: "-73.534056",
|
||||
Output: "float",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return latitude(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("latituderange", Info{
|
||||
Display: "Latitude Range",
|
||||
Category: "address",
|
||||
Description: "Latitude number between the given range (default min=0, max=90)",
|
||||
Example: "22.921026",
|
||||
Output: "float",
|
||||
Params: []Param{
|
||||
{Field: "min", Display: "Min", Type: "float", Default: "0", Description: "Minimum range"},
|
||||
{Field: "max", Display: "Max", Type: "float", Default: "90", Description: "Maximum range"},
|
||||
},
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
min, err := info.GetFloat64(m, "min")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
max, err := info.GetFloat64(m, "max")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rangeOut, err := latitudeInRange(f, min, max)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rangeOut, nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("longitude", Info{
|
||||
Display: "Longitude",
|
||||
Category: "address",
|
||||
Description: "Geographic coordinate indicating east-west position on Earth's surface",
|
||||
Example: "-147.068112",
|
||||
Output: "float",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return longitude(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("longituderange", Info{
|
||||
Display: "Longitude Range",
|
||||
Category: "address",
|
||||
Description: "Longitude number between the given range (default min=0, max=180)",
|
||||
Example: "-8.170450",
|
||||
Output: "float",
|
||||
Params: []Param{
|
||||
{Field: "min", Display: "Min", Type: "float", Default: "0", Description: "Minimum range"},
|
||||
{Field: "max", Display: "Max", Type: "float", Default: "180", Description: "Maximum range"},
|
||||
},
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
min, err := info.GetFloat64(m, "min")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
max, err := info.GetFloat64(m, "max")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rangeOut, err := longitudeInRange(f, min, max)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rangeOut, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
package gofakeit
|
||||
|
||||
// PetName will return a random fun pet name
|
||||
func PetName() string {
|
||||
return petName(GlobalFaker)
|
||||
}
|
||||
|
||||
// PetName will return a random fun pet name
|
||||
func (f *Faker) PetName() string {
|
||||
return petName(f)
|
||||
}
|
||||
|
||||
func petName(f *Faker) string {
|
||||
return getRandValue(f, []string{"animal", "petname"})
|
||||
}
|
||||
|
||||
// Animal will return a random animal
|
||||
func Animal() string {
|
||||
return animal(GlobalFaker)
|
||||
}
|
||||
|
||||
// Animal will return a random animal
|
||||
func (f *Faker) Animal() string {
|
||||
return animal(f)
|
||||
}
|
||||
|
||||
func animal(f *Faker) string {
|
||||
return getRandValue(f, []string{"animal", "animal"})
|
||||
}
|
||||
|
||||
// AnimalType will return a random animal type
|
||||
func AnimalType() string {
|
||||
return animalType(GlobalFaker)
|
||||
}
|
||||
|
||||
// AnimalType will return a random animal type
|
||||
func (f *Faker) AnimalType() string {
|
||||
return animalType(f)
|
||||
}
|
||||
|
||||
func animalType(f *Faker) string {
|
||||
return getRandValue(f, []string{"animal", "type"})
|
||||
}
|
||||
|
||||
// FarmAnimal will return a random animal that usually lives on a farm
|
||||
func FarmAnimal() string {
|
||||
return farmAnimal(GlobalFaker)
|
||||
}
|
||||
|
||||
// FarmAnimal will return a random animal that usually lives on a farm
|
||||
func (f *Faker) FarmAnimal() string {
|
||||
return farmAnimal(f)
|
||||
}
|
||||
|
||||
func farmAnimal(f *Faker) string {
|
||||
return getRandValue(f, []string{"animal", "farm"})
|
||||
}
|
||||
|
||||
// Cat will return a random cat breed
|
||||
func Cat() string {
|
||||
return cat(GlobalFaker)
|
||||
}
|
||||
|
||||
// Cat will return a random cat breed
|
||||
func (f *Faker) Cat() string {
|
||||
return cat(f)
|
||||
}
|
||||
|
||||
func cat(f *Faker) string {
|
||||
return getRandValue(f, []string{"animal", "cat"})
|
||||
}
|
||||
|
||||
// Dog will return a random dog breed
|
||||
func Dog() string {
|
||||
return dog(GlobalFaker)
|
||||
}
|
||||
|
||||
// Dog will return a random dog breed
|
||||
func (f *Faker) Dog() string {
|
||||
return dog(f)
|
||||
}
|
||||
|
||||
func dog(f *Faker) string {
|
||||
return getRandValue(f, []string{"animal", "dog"})
|
||||
}
|
||||
|
||||
// Bird will return a random bird species
|
||||
func Bird() string {
|
||||
return bird(GlobalFaker)
|
||||
}
|
||||
|
||||
// Bird will return a random bird species
|
||||
func (f *Faker) Bird() string {
|
||||
return bird(f)
|
||||
}
|
||||
|
||||
func bird(f *Faker) string {
|
||||
return getRandValue(f, []string{"animal", "bird"})
|
||||
}
|
||||
|
||||
func addAnimalLookup() {
|
||||
AddFuncLookup("petname", Info{
|
||||
Display: "Pet Name",
|
||||
Category: "animal",
|
||||
Description: "Affectionate nickname given to a pet",
|
||||
Example: "Ozzy Pawsborne",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return petName(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("animal", Info{
|
||||
Display: "Animal",
|
||||
Category: "animal",
|
||||
Description: "Living creature with the ability to move, eat, and interact with its environment",
|
||||
Example: "elk",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return animal(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("animaltype", Info{
|
||||
Display: "Animal Type",
|
||||
Category: "animal",
|
||||
Description: "Type of animal, such as mammals, birds, reptiles, etc.",
|
||||
Example: "amphibians",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return animalType(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("farmanimal", Info{
|
||||
Display: "Farm Animal",
|
||||
Category: "animal",
|
||||
Description: "Animal name commonly found on a farm",
|
||||
Example: "Chicken",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return farmAnimal(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("cat", Info{
|
||||
Display: "Cat",
|
||||
Category: "animal",
|
||||
Description: "Various breeds that define different cats",
|
||||
Example: "Chausie",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return cat(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("dog", Info{
|
||||
Display: "Dog",
|
||||
Category: "animal",
|
||||
Description: "Various breeds that define different dogs",
|
||||
Example: "Norwich Terrier",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return dog(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("bird", Info{
|
||||
Display: "Bird",
|
||||
Category: "animal",
|
||||
Description: "Distinct species of birds",
|
||||
Example: "goose",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return bird(f), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package gofakeit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AppName will generate a random app name
|
||||
func AppName() string {
|
||||
return appName(GlobalFaker)
|
||||
}
|
||||
|
||||
// AppName will generate a random app name
|
||||
func (f *Faker) AppName() string {
|
||||
return appName(f)
|
||||
}
|
||||
|
||||
func appName(f *Faker) string {
|
||||
name := ""
|
||||
switch number(f, 1, 3) {
|
||||
case 1:
|
||||
name = noun(f) + verb(f)
|
||||
case 2:
|
||||
name = color(f) + noun(f)
|
||||
case 3:
|
||||
name = animal(f) + verb(f)
|
||||
}
|
||||
|
||||
return title(name)
|
||||
}
|
||||
|
||||
// AppVersion will generate a random app version
|
||||
func AppVersion() string {
|
||||
return appVersion(GlobalFaker)
|
||||
}
|
||||
|
||||
// AppVersion will generate a random app version
|
||||
func (f *Faker) AppVersion() string {
|
||||
return appVersion(f)
|
||||
}
|
||||
|
||||
func appVersion(f *Faker) string {
|
||||
return fmt.Sprintf("%d.%d.%d", number(f, 1, 5), number(f, 1, 20), number(f, 1, 20))
|
||||
}
|
||||
|
||||
// AppAuthor will generate a random company or person name
|
||||
func AppAuthor() string {
|
||||
return appAuthor(GlobalFaker)
|
||||
}
|
||||
|
||||
// AppAuthor will generate a random company or person name
|
||||
func (f *Faker) AppAuthor() string {
|
||||
return appAuthor(f)
|
||||
}
|
||||
|
||||
func appAuthor(f *Faker) string {
|
||||
if boolFunc(f) {
|
||||
return name(f)
|
||||
}
|
||||
|
||||
return company(f)
|
||||
}
|
||||
|
||||
func addAppLookup() {
|
||||
AddFuncLookup("appname", Info{
|
||||
Display: "App Name",
|
||||
Category: "app",
|
||||
Description: "Software program designed for a specific purpose or task on a computer or mobile device",
|
||||
Example: "Parkrespond",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return appName(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("appversion", Info{
|
||||
Display: "App Version",
|
||||
Category: "app",
|
||||
Description: "Particular release of an application in Semantic Versioning format",
|
||||
Example: "1.12.14",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return appVersion(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("appauthor", Info{
|
||||
Display: "App Author",
|
||||
Category: "app",
|
||||
Description: "Person or group creating and developing an application",
|
||||
Example: "Qado Energy, Inc.",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return appAuthor(f), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package gofakeit
|
||||
|
||||
// Username will generate a random username based upon picking a random lastname and random numbers at the end
|
||||
func Username() string {
|
||||
return username(GlobalFaker)
|
||||
}
|
||||
|
||||
// Username will generate a random username based upon picking a random lastname and random numbers at the end
|
||||
func (f *Faker) Username() string {
|
||||
return username(f)
|
||||
}
|
||||
|
||||
func username(f *Faker) string {
|
||||
return getRandValue(f, []string{"person", "last"}) + replaceWithNumbers(f, "####")
|
||||
}
|
||||
|
||||
// Password will generate a random password.
|
||||
// Minimum number length of 5 if less than.
|
||||
func Password(lower bool, upper bool, numeric bool, special bool, space bool, num int) string {
|
||||
return password(GlobalFaker, lower, upper, numeric, special, space, num)
|
||||
}
|
||||
|
||||
// Password will generate a random password.
|
||||
// Minimum number length of 5 if less than.
|
||||
func (f *Faker) Password(lower bool, upper bool, numeric bool, special bool, space bool, num int) string {
|
||||
return password(f, lower, upper, numeric, special, space, num)
|
||||
}
|
||||
|
||||
func password(f *Faker, lower bool, upper bool, numeric bool, special bool, space bool, num int) string {
|
||||
// Make sure the num minimum is at least 5
|
||||
if num < 5 {
|
||||
num = 5
|
||||
}
|
||||
|
||||
// Setup weights
|
||||
items := make([]any, 0)
|
||||
weights := make([]float32, 0)
|
||||
if lower {
|
||||
items = append(items, "l")
|
||||
weights = append(weights, 4)
|
||||
}
|
||||
if upper {
|
||||
items = append(items, "u")
|
||||
weights = append(weights, 4)
|
||||
}
|
||||
if numeric {
|
||||
items = append(items, "n")
|
||||
weights = append(weights, 3)
|
||||
}
|
||||
if special {
|
||||
items = append(items, "e")
|
||||
weights = append(weights, 2)
|
||||
}
|
||||
if space {
|
||||
items = append(items, "a")
|
||||
weights = append(weights, 1)
|
||||
}
|
||||
|
||||
// If no items are selected then default to lower, upper, numeric
|
||||
if len(items) == 0 {
|
||||
items = append(items, "l", "u", "n")
|
||||
weights = append(weights, 4, 4, 3)
|
||||
}
|
||||
|
||||
// Create byte slice
|
||||
b := make([]byte, num)
|
||||
|
||||
for i := 0; i <= num-1; i++ {
|
||||
// Run weighted
|
||||
weight, _ := weighted(f, items, weights)
|
||||
|
||||
switch weight.(string) {
|
||||
case "l":
|
||||
b[i] = lowerStr[f.Int64()%int64(len(lowerStr))]
|
||||
case "u":
|
||||
b[i] = upperStr[f.Int64()%int64(len(upperStr))]
|
||||
case "n":
|
||||
b[i] = numericStr[f.Int64()%int64(len(numericStr))]
|
||||
case "e":
|
||||
b[i] = specialSafeStr[f.Int64()%int64(len(specialSafeStr))]
|
||||
case "a":
|
||||
b[i] = spaceStr[f.Int64()%int64(len(spaceStr))]
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle bytes
|
||||
for i := range b {
|
||||
j := f.IntN(i + 1)
|
||||
b[i], b[j] = b[j], b[i]
|
||||
}
|
||||
|
||||
// Replace first or last character if it's a space, and other options are available
|
||||
if b[0] == ' ' {
|
||||
b[0] = password(f, lower, upper, numeric, special, false, 1)[0]
|
||||
}
|
||||
if b[len(b)-1] == ' ' {
|
||||
b[len(b)-1] = password(f, lower, upper, numeric, special, false, 1)[0]
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func addAuthLookup() {
|
||||
AddFuncLookup("username", Info{
|
||||
Display: "Username",
|
||||
Category: "auth",
|
||||
Description: "Unique identifier assigned to a user for accessing an account or system",
|
||||
Example: "Daniel1364",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return username(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("password", Info{
|
||||
Display: "Password",
|
||||
Category: "auth",
|
||||
Description: "Secret word or phrase used to authenticate access to a system or account",
|
||||
Example: "EEP+wwpk 4lU-eHNXlJZ4n K9%v&TZ9e",
|
||||
Output: "string",
|
||||
Params: []Param{
|
||||
{Field: "lower", Display: "Lower", Type: "bool", Default: "true", Description: "Whether or not to add lower case characters"},
|
||||
{Field: "upper", Display: "Upper", Type: "bool", Default: "true", Description: "Whether or not to add upper case characters"},
|
||||
{Field: "numeric", Display: "Numeric", Type: "bool", Default: "true", Description: "Whether or not to add numeric characters"},
|
||||
{Field: "special", Display: "Special", Type: "bool", Default: "true", Description: "Whether or not to add special characters"},
|
||||
{Field: "space", Display: "Space", Type: "bool", Default: "false", Description: "Whether or not to add spaces"},
|
||||
{Field: "length", Display: "Length", Type: "int", Default: "12", Description: "Number of characters in password"},
|
||||
},
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
lower, err := info.GetBool(m, "lower")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
upper, err := info.GetBool(m, "upper")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
numeric, err := info.GetBool(m, "numeric")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
special, err := info.GetBool(m, "special")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
space, err := info.GetBool(m, "space")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
length, err := info.GetInt(m, "length")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return password(f, lower, upper, numeric, special, space, length), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
package gofakeit
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// BeerName will return a random beer name
|
||||
func BeerName() string {
|
||||
return beerName(GlobalFaker)
|
||||
}
|
||||
|
||||
// BeerName will return a random beer name
|
||||
func (f *Faker) BeerName() string {
|
||||
return beerName(f)
|
||||
}
|
||||
|
||||
func beerName(f *Faker) string {
|
||||
return getRandValue(f, []string{"beer", "name"})
|
||||
}
|
||||
|
||||
// BeerStyle will return a random beer style
|
||||
func BeerStyle() string {
|
||||
return beerStyle(GlobalFaker)
|
||||
}
|
||||
|
||||
// BeerStyle will return a random beer style
|
||||
func (f *Faker) BeerStyle() string {
|
||||
return beerStyle(f)
|
||||
}
|
||||
|
||||
func beerStyle(f *Faker) string {
|
||||
return getRandValue(f, []string{"beer", "style"})
|
||||
}
|
||||
|
||||
// BeerHop will return a random beer hop
|
||||
func BeerHop() string {
|
||||
return beerHop(GlobalFaker)
|
||||
}
|
||||
|
||||
// BeerHop will return a random beer hop
|
||||
func (f *Faker) BeerHop() string {
|
||||
return beerHop(f)
|
||||
}
|
||||
|
||||
func beerHop(f *Faker) string {
|
||||
return getRandValue(f, []string{"beer", "hop"})
|
||||
}
|
||||
|
||||
// BeerYeast will return a random beer yeast
|
||||
func BeerYeast() string {
|
||||
return beerYeast(GlobalFaker)
|
||||
}
|
||||
|
||||
// BeerYeast will return a random beer yeast
|
||||
func (f *Faker) BeerYeast() string {
|
||||
return beerYeast(f)
|
||||
}
|
||||
|
||||
func beerYeast(f *Faker) string {
|
||||
return getRandValue(f, []string{"beer", "yeast"})
|
||||
}
|
||||
|
||||
// BeerMalt will return a random beer malt
|
||||
func BeerMalt() string {
|
||||
return beerMalt(GlobalFaker)
|
||||
}
|
||||
|
||||
// BeerMalt will return a random beer malt
|
||||
func (f *Faker) BeerMalt() string {
|
||||
return beerMalt(f)
|
||||
}
|
||||
|
||||
func beerMalt(f *Faker) string {
|
||||
return getRandValue(f, []string{"beer", "malt"})
|
||||
}
|
||||
|
||||
// BeerAlcohol will return a random beer alcohol level between 2.0 and 10.0
|
||||
func BeerAlcohol() string {
|
||||
return beerAlcohol(GlobalFaker)
|
||||
}
|
||||
|
||||
// BeerAlcohol will return a random beer alcohol level between 2.0 and 10.0
|
||||
func (f *Faker) BeerAlcohol() string {
|
||||
return beerAlcohol(f)
|
||||
}
|
||||
|
||||
func beerAlcohol(f *Faker) string {
|
||||
return strconv.FormatFloat(float64Range(f, 2.0, 10.0), 'f', 1, 64) + "%"
|
||||
}
|
||||
|
||||
// BeerIbu will return a random beer ibu value between 10 and 100
|
||||
func BeerIbu() string {
|
||||
return beerIbu(GlobalFaker)
|
||||
}
|
||||
|
||||
// BeerIbu will return a random beer ibu value between 10 and 100
|
||||
func (f *Faker) BeerIbu() string {
|
||||
return beerIbu(f)
|
||||
}
|
||||
|
||||
func beerIbu(f *Faker) string {
|
||||
return strconv.Itoa(randIntRange(f, 10, 100)) + " IBU"
|
||||
}
|
||||
|
||||
// BeerBlg will return a random beer blg between 5.0 and 20.0
|
||||
func BeerBlg() string {
|
||||
return beerBlg(GlobalFaker)
|
||||
}
|
||||
|
||||
// BeerBlg will return a random beer blg between 5.0 and 20.0
|
||||
func (f *Faker) BeerBlg() string {
|
||||
return beerBlg(f)
|
||||
}
|
||||
|
||||
func beerBlg(f *Faker) string {
|
||||
return strconv.FormatFloat(float64Range(f, 5.0, 20.0), 'f', 1, 64) + "°Blg"
|
||||
}
|
||||
|
||||
func addBeerLookup() {
|
||||
AddFuncLookup("beername", Info{
|
||||
Display: "Beer Name",
|
||||
Category: "beer",
|
||||
Description: "Specific brand or variety of beer",
|
||||
Example: "Duvel",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return beerName(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("beerstyle", Info{
|
||||
Display: "Beer Style",
|
||||
Category: "beer",
|
||||
Description: "Distinct characteristics and flavors of beer",
|
||||
Example: "European Amber Lager",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return beerStyle(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("beerhop", Info{
|
||||
Display: "Beer Hop",
|
||||
Category: "beer",
|
||||
Description: "The flower used in brewing to add flavor, aroma, and bitterness to beer",
|
||||
Example: "Glacier",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return beerHop(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("beeryeast", Info{
|
||||
Display: "Beer Yeast",
|
||||
Category: "beer",
|
||||
Description: "Microorganism used in brewing to ferment sugars, producing alcohol and carbonation in beer",
|
||||
Example: "1388 - Belgian Strong Ale",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return beerYeast(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("beermalt", Info{
|
||||
Display: "Beer Malt",
|
||||
Category: "beer",
|
||||
Description: "Processed barley or other grains, provides sugars for fermentation and flavor to beer",
|
||||
Example: "Munich",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return beerMalt(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("beeralcohol", Info{
|
||||
Display: "Beer Alcohol",
|
||||
Category: "beer",
|
||||
Description: "Measures the alcohol content in beer",
|
||||
Example: "2.7%",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return beerAlcohol(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("beeribu", Info{
|
||||
Display: "Beer IBU",
|
||||
Category: "beer",
|
||||
Description: "Scale measuring bitterness of beer from hops",
|
||||
Example: "29 IBU",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return beerIbu(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("beerblg", Info{
|
||||
Display: "Beer BLG",
|
||||
Category: "beer",
|
||||
Description: "Scale indicating the concentration of extract in worts",
|
||||
Example: "6.4°Blg",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return beerBlg(f), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package gofakeit
|
||||
|
||||
func BookTitle() string { return bookTitle(GlobalFaker) }
|
||||
|
||||
func (f *Faker) BookTitle() string { return bookTitle(f) }
|
||||
|
||||
func bookTitle(f *Faker) string { return getRandValue(f, []string{"book", "title"}) }
|
||||
|
||||
func BookAuthor() string { return bookAuthor(GlobalFaker) }
|
||||
|
||||
func (f *Faker) BookAuthor() string { return bookAuthor(f) }
|
||||
|
||||
func bookAuthor(f *Faker) string { return getRandValue(f, []string{"book", "author"}) }
|
||||
|
||||
func BookGenre() string { return bookGenre(GlobalFaker) }
|
||||
|
||||
func (f *Faker) BookGenre() string { return bookGenre(f) }
|
||||
|
||||
func bookGenre(f *Faker) string { return getRandValue(f, []string{"book", "genre"}) }
|
||||
|
||||
type BookInfo struct {
|
||||
Title string `json:"title" xml:"name"`
|
||||
Author string `json:"author" xml:"author"`
|
||||
Genre string `json:"genre" xml:"genre"`
|
||||
}
|
||||
|
||||
func Book() *BookInfo { return book(GlobalFaker) }
|
||||
|
||||
func (f *Faker) Book() *BookInfo { return book(f) }
|
||||
|
||||
func book(f *Faker) *BookInfo {
|
||||
return &BookInfo{
|
||||
Title: bookTitle(f),
|
||||
Author: bookAuthor(f),
|
||||
Genre: bookGenre(f),
|
||||
}
|
||||
}
|
||||
|
||||
func addBookLookup() {
|
||||
AddFuncLookup("book", Info{
|
||||
Display: "Book",
|
||||
Category: "book",
|
||||
Description: "Written or printed work consisting of pages bound together, covering various subjects or stories",
|
||||
Example: `{
|
||||
"title": "Anna Karenina",
|
||||
"author": "Toni Morrison",
|
||||
"genre": "Thriller"
|
||||
}`,
|
||||
Output: "map[string]string",
|
||||
ContentType: "application/json",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return book(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("booktitle", Info{
|
||||
Display: "Title",
|
||||
Category: "book",
|
||||
Description: "The specific name given to a book",
|
||||
Example: "Hamlet",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return bookTitle(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("bookauthor", Info{
|
||||
Display: "Author",
|
||||
Category: "book",
|
||||
Description: "The individual who wrote or created the content of a book",
|
||||
Example: "Mark Twain",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return bookAuthor(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("bookgenre", Info{
|
||||
Display: "Genre",
|
||||
Category: "book",
|
||||
Description: "Category or type of book defined by its content, style, or form",
|
||||
Example: "Adventure",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return bookGenre(f), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package gofakeit
|
||||
|
||||
// CarInfo is a struct dataset of all car information
|
||||
type CarInfo struct {
|
||||
Type string `json:"type" xml:"type"`
|
||||
Fuel string `json:"fuel" xml:"fuel"`
|
||||
Transmission string `json:"transmission" xml:"transmission"`
|
||||
Brand string `json:"brand" xml:"brand"`
|
||||
Model string `json:"model" xml:"model"`
|
||||
Year int `json:"year" xml:"year"`
|
||||
}
|
||||
|
||||
// Car will generate a struct with car information
|
||||
func Car() *CarInfo { return car(GlobalFaker) }
|
||||
|
||||
// Car will generate a struct with car information
|
||||
func (f *Faker) Car() *CarInfo { return car(f) }
|
||||
|
||||
func car(f *Faker) *CarInfo {
|
||||
return &CarInfo{
|
||||
Type: carType(f),
|
||||
Fuel: carFuelType(f),
|
||||
Transmission: carTransmissionType(f),
|
||||
Brand: carMaker(f),
|
||||
Model: carModel(f),
|
||||
Year: year(f),
|
||||
}
|
||||
}
|
||||
|
||||
// CarType will generate a random car type string
|
||||
func CarType() string { return carType(GlobalFaker) }
|
||||
|
||||
// CarType will generate a random car type string
|
||||
func (f *Faker) CarType() string { return carType(f) }
|
||||
|
||||
func carType(f *Faker) string { return getRandValue(f, []string{"car", "type"}) }
|
||||
|
||||
// CarFuelType will return a random fuel type
|
||||
func CarFuelType() string { return carFuelType(GlobalFaker) }
|
||||
|
||||
// CarFuelType will return a random fuel type
|
||||
func (f *Faker) CarFuelType() string { return carFuelType(f) }
|
||||
|
||||
func carFuelType(f *Faker) string { return getRandValue(f, []string{"car", "fuel_type"}) }
|
||||
|
||||
// CarTransmissionType will return a random transmission type
|
||||
func CarTransmissionType() string { return carTransmissionType(GlobalFaker) }
|
||||
|
||||
// CarTransmissionType will return a random transmission type
|
||||
func (f *Faker) CarTransmissionType() string { return carTransmissionType(f) }
|
||||
|
||||
func carTransmissionType(f *Faker) string {
|
||||
return getRandValue(f, []string{"car", "transmission_type"})
|
||||
}
|
||||
|
||||
// CarMaker will return a random car maker
|
||||
func CarMaker() string { return carMaker(GlobalFaker) }
|
||||
|
||||
// CarMaker will return a random car maker
|
||||
func (f *Faker) CarMaker() string { return carMaker(f) }
|
||||
|
||||
func carMaker(f *Faker) string { return getRandValue(f, []string{"car", "maker"}) }
|
||||
|
||||
// CarModel will return a random car model
|
||||
func CarModel() string { return carModel(GlobalFaker) }
|
||||
|
||||
// CarModel will return a random car model
|
||||
func (f *Faker) CarModel() string { return carModel(f) }
|
||||
|
||||
func carModel(f *Faker) string { return getRandValue(f, []string{"car", "model"}) }
|
||||
|
||||
func addCarLookup() {
|
||||
AddFuncLookup("car", Info{
|
||||
Display: "Car",
|
||||
Category: "car",
|
||||
Description: "Wheeled motor vehicle used for transportation",
|
||||
Example: `{
|
||||
"type": "Passenger car mini",
|
||||
"fuel": "Gasoline",
|
||||
"transmission": "Automatic",
|
||||
"brand": "Fiat",
|
||||
"model": "Freestyle Fwd",
|
||||
"year": 1991
|
||||
}`,
|
||||
Output: "map[string]any",
|
||||
ContentType: "application/json",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return car(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("cartype", Info{
|
||||
Display: "Car Type",
|
||||
Category: "car",
|
||||
Description: "Classification of cars based on size, use, or body style",
|
||||
Example: "Passenger car mini",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return carType(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("carfueltype", Info{
|
||||
Display: "Car Fuel Type",
|
||||
Category: "car",
|
||||
Description: "Type of energy source a car uses",
|
||||
Example: "CNG",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return carFuelType(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("cartransmissiontype", Info{
|
||||
Display: "Car Transmission Type",
|
||||
Category: "car",
|
||||
Description: "Mechanism a car uses to transmit power from the engine to the wheels",
|
||||
Example: "Manual",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return carTransmissionType(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("carmaker", Info{
|
||||
Display: "Car Maker",
|
||||
Category: "car",
|
||||
Description: "Company or brand that manufactures and designs cars",
|
||||
Example: "Nissan",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return carMaker(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("carmodel", Info{
|
||||
Display: "Car Model",
|
||||
Category: "car",
|
||||
Description: "Specific design or version of a car produced by a manufacturer",
|
||||
Example: "Aveo",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return carModel(f), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package gofakeit
|
||||
|
||||
// CelebrityActor will generate a random celebrity actor
|
||||
func CelebrityActor() string { return celebrityActor(GlobalFaker) }
|
||||
|
||||
// CelebrityActor will generate a random celebrity actor
|
||||
func (f *Faker) CelebrityActor() string { return celebrityActor(f) }
|
||||
|
||||
func celebrityActor(f *Faker) string { return getRandValue(f, []string{"celebrity", "actor"}) }
|
||||
|
||||
// CelebrityBusiness will generate a random celebrity business person
|
||||
func CelebrityBusiness() string { return celebrityBusiness(GlobalFaker) }
|
||||
|
||||
// CelebrityBusiness will generate a random celebrity business person
|
||||
func (f *Faker) CelebrityBusiness() string { return celebrityBusiness(f) }
|
||||
|
||||
func celebrityBusiness(f *Faker) string {
|
||||
return getRandValue(f, []string{"celebrity", "business"})
|
||||
}
|
||||
|
||||
// CelebritySport will generate a random celebrity sport person
|
||||
func CelebritySport() string { return celebritySport(GlobalFaker) }
|
||||
|
||||
// CelebritySport will generate a random celebrity sport person
|
||||
func (f *Faker) CelebritySport() string { return celebritySport(f) }
|
||||
|
||||
func celebritySport(f *Faker) string { return getRandValue(f, []string{"celebrity", "sport"}) }
|
||||
|
||||
func addCelebrityLookup() {
|
||||
AddFuncLookup("celebrityactor", Info{
|
||||
Display: "Celebrity Actor",
|
||||
Category: "celebrity",
|
||||
Description: "Famous person known for acting in films, television, or theater",
|
||||
Example: "Brad Pitt",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return celebrityActor(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("celebritybusiness", Info{
|
||||
Display: "Celebrity Business",
|
||||
Category: "celebrity",
|
||||
Description: "High-profile individual known for significant achievements in business or entrepreneurship",
|
||||
Example: "Elon Musk",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return celebrityBusiness(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("celebritysport", Info{
|
||||
Display: "Celebrity Sport",
|
||||
Category: "celebrity",
|
||||
Description: "Famous athlete known for achievements in a particular sport",
|
||||
Example: "Michael Phelps",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return celebritySport(f), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package gofakeit
|
||||
|
||||
import (
|
||||
"github.com/brianvoe/gofakeit/v7/data"
|
||||
)
|
||||
|
||||
// Color will generate a random color string
|
||||
func Color() string { return color(GlobalFaker) }
|
||||
|
||||
// Color will generate a random color string
|
||||
func (f *Faker) Color() string { return color(f) }
|
||||
|
||||
func color(f *Faker) string { return getRandValue(f, []string{"color", "full"}) }
|
||||
|
||||
// NiceColor will generate a random safe color string
|
||||
func NiceColors() []string { return niceColors(GlobalFaker) }
|
||||
|
||||
// NiceColor will generate a random safe color string
|
||||
func (f *Faker) NiceColors() []string { return niceColors(f) }
|
||||
|
||||
func niceColors(f *Faker) []string {
|
||||
return data.ColorsNice[randIntRange(f, 0, len(data.ColorsNice)-1)]
|
||||
}
|
||||
|
||||
// SafeColor will generate a random safe color string
|
||||
func SafeColor() string { return safeColor(GlobalFaker) }
|
||||
|
||||
// SafeColor will generate a random safe color string
|
||||
func (f *Faker) SafeColor() string { return safeColor(f) }
|
||||
|
||||
func safeColor(f *Faker) string { return getRandValue(f, []string{"color", "safe"}) }
|
||||
|
||||
// HexColor will generate a random hexadecimal color string
|
||||
func HexColor() string { return hexColor(GlobalFaker) }
|
||||
|
||||
// HexColor will generate a random hexadecimal color string
|
||||
func (f *Faker) HexColor() string { return hexColor(f) }
|
||||
|
||||
func hexColor(f *Faker) string {
|
||||
color := make([]byte, 6)
|
||||
hashQuestion := []byte("?#")
|
||||
for i := 0; i < 6; i++ {
|
||||
color[i] = hashQuestion[f.IntN(2)]
|
||||
}
|
||||
|
||||
return "#" + replaceWithHexLetters(f, replaceWithNumbers(f, string(color)))
|
||||
}
|
||||
|
||||
// RGBColor will generate a random int slice color
|
||||
func RGBColor() []int { return rgbColor(GlobalFaker) }
|
||||
|
||||
// RGBColor will generate a random int slice color
|
||||
func (f *Faker) RGBColor() []int { return rgbColor(f) }
|
||||
|
||||
func rgbColor(f *Faker) []int {
|
||||
return []int{randIntRange(f, 0, 255), randIntRange(f, 0, 255), randIntRange(f, 0, 255)}
|
||||
}
|
||||
|
||||
func addColorLookup() {
|
||||
AddFuncLookup("color", Info{
|
||||
Display: "Color",
|
||||
Category: "color",
|
||||
Description: "Hue seen by the eye, returns the name of the color like red or blue",
|
||||
Example: "MediumOrchid",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return color(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("nicecolors", Info{
|
||||
Display: "Nice Colors",
|
||||
Category: "color",
|
||||
Description: "Attractive and appealing combinations of colors, returns an list of color hex codes",
|
||||
Example: `["#cfffdd","#b4dec1","#5c5863","#a85163","#ff1f4c"]`,
|
||||
Output: "[]string",
|
||||
ContentType: "application/json",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return color(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("safecolor", Info{
|
||||
Display: "Safe Color",
|
||||
Category: "color",
|
||||
Description: "Colors displayed consistently on different web browsers and devices",
|
||||
Example: "black",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return safeColor(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("hexcolor", Info{
|
||||
Display: "Hex Color",
|
||||
Category: "color",
|
||||
Description: "Six-digit code representing a color in the color model",
|
||||
Example: "#a99fb4",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return hexColor(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("rgbcolor", Info{
|
||||
Display: "RGB Color",
|
||||
Category: "color",
|
||||
Description: "Color defined by red, green, and blue light values",
|
||||
Example: "[85, 224, 195]",
|
||||
Output: "[]int",
|
||||
ContentType: "application/json",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return rgbColor(f), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
package gofakeit
|
||||
|
||||
// Company will generate a random company name string
|
||||
func Company() string { return company(GlobalFaker) }
|
||||
|
||||
// Company will generate a random company name string
|
||||
func (f *Faker) Company() string { return company(f) }
|
||||
|
||||
func company(f *Faker) string { return getRandValue(f, []string{"company", "name"}) }
|
||||
|
||||
// CompanySuffix will generate a random company suffix string
|
||||
func CompanySuffix() string { return companySuffix(GlobalFaker) }
|
||||
|
||||
// CompanySuffix will generate a random company suffix string
|
||||
func (f *Faker) CompanySuffix() string { return companySuffix(f) }
|
||||
|
||||
func companySuffix(f *Faker) string { return getRandValue(f, []string{"company", "suffix"}) }
|
||||
|
||||
// Blurb will generate a random company blurb string
|
||||
func Blurb() string { return blurb(GlobalFaker) }
|
||||
|
||||
func (f *Faker) Blurb() string { return blurb(f) }
|
||||
|
||||
func blurb(f *Faker) string { return getRandValue(f, []string{"company", "blurb"}) }
|
||||
|
||||
// BuzzWord will generate a random company buzz word string
|
||||
func BuzzWord() string { return buzzWord(GlobalFaker) }
|
||||
|
||||
// BuzzWord will generate a random company buzz word string
|
||||
func (f *Faker) BuzzWord() string { return buzzWord(f) }
|
||||
|
||||
func buzzWord(f *Faker) string { return getRandValue(f, []string{"company", "buzzwords"}) }
|
||||
|
||||
// BS will generate a random company bs string
|
||||
func BS() string { return bs(GlobalFaker) }
|
||||
|
||||
// BS will generate a random company bs string
|
||||
func (f *Faker) BS() string { return bs(f) }
|
||||
|
||||
func bs(f *Faker) string { return getRandValue(f, []string{"company", "bs"}) }
|
||||
|
||||
// JobInfo is a struct of job information
|
||||
type JobInfo struct {
|
||||
Company string `json:"company" xml:"company"`
|
||||
Title string `json:"title" xml:"title"`
|
||||
Descriptor string `json:"descriptor" xml:"descriptor"`
|
||||
Level string `json:"level" xml:"level"`
|
||||
}
|
||||
|
||||
// Job will generate a struct with random job information
|
||||
func Job() *JobInfo { return job(GlobalFaker) }
|
||||
|
||||
// Job will generate a struct with random job information
|
||||
func (f *Faker) Job() *JobInfo { return job(f) }
|
||||
|
||||
func job(f *Faker) *JobInfo {
|
||||
return &JobInfo{
|
||||
Company: company(f),
|
||||
Title: jobTitle(f),
|
||||
Descriptor: jobDescriptor(f),
|
||||
Level: jobLevel(f),
|
||||
}
|
||||
}
|
||||
|
||||
// JobTitle will generate a random job title string
|
||||
func JobTitle() string { return jobTitle(GlobalFaker) }
|
||||
|
||||
// JobTitle will generate a random job title string
|
||||
func (f *Faker) JobTitle() string { return jobTitle(f) }
|
||||
|
||||
func jobTitle(f *Faker) string { return getRandValue(f, []string{"job", "title"}) }
|
||||
|
||||
// JobDescriptor will generate a random job descriptor string
|
||||
func JobDescriptor() string { return jobDescriptor(GlobalFaker) }
|
||||
|
||||
// JobDescriptor will generate a random job descriptor string
|
||||
func (f *Faker) JobDescriptor() string { return jobDescriptor(f) }
|
||||
|
||||
func jobDescriptor(f *Faker) string { return getRandValue(f, []string{"job", "descriptor"}) }
|
||||
|
||||
// JobLevel will generate a random job level string
|
||||
func JobLevel() string { return jobLevel(GlobalFaker) }
|
||||
|
||||
// JobLevel will generate a random job level string
|
||||
func (f *Faker) JobLevel() string { return jobLevel(f) }
|
||||
|
||||
func jobLevel(f *Faker) string { return getRandValue(f, []string{"job", "level"}) }
|
||||
|
||||
// Slogan will generate a random company slogan
|
||||
func Slogan() string { return slogan(GlobalFaker) }
|
||||
|
||||
// Slogan will generate a random company slogan
|
||||
func (f *Faker) Slogan() string { return slogan(f) }
|
||||
|
||||
// Slogan will generate a random company slogan
|
||||
func slogan(f *Faker) string {
|
||||
slogan := ""
|
||||
var sloganStyle = number(f, 0, 2)
|
||||
switch sloganStyle {
|
||||
// Noun. Buzzword!
|
||||
case 0:
|
||||
slogan = getRandValue(f, []string{"company", "blurb"}) + ". " + getRandValue(f, []string{"company", "buzzwords"}) + "!"
|
||||
// Buzzword Noun, Buzzword Noun.
|
||||
case 1:
|
||||
slogan = getRandValue(f, []string{"company", "buzzwords"}) + " " + getRandValue(f, []string{"company", "blurb"}) + ", " + getRandValue(f, []string{"company", "buzzwords"}) + " " + getRandValue(f, []string{"company", "blurb"}) + "."
|
||||
// Buzzword bs Noun, Buzzword.
|
||||
case 2:
|
||||
slogan = getRandValue(f, []string{"company", "buzzwords"}) + " " + getRandValue(f, []string{"company", "bs"}) + " " + getRandValue(f, []string{"company", "blurb"}) + ", " + getRandValue(f, []string{"company", "buzzwords"}) + "."
|
||||
}
|
||||
return slogan
|
||||
}
|
||||
|
||||
func addCompanyLookup() {
|
||||
AddFuncLookup("company", Info{
|
||||
Display: "Company",
|
||||
Category: "company",
|
||||
Description: "Designated official name of a business or organization",
|
||||
Example: "Moen, Pagac and Wuckert",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return company(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("companysuffix", Info{
|
||||
Display: "Company Suffix",
|
||||
Category: "company",
|
||||
Description: "Suffix at the end of a company name, indicating business structure, like 'Inc.' or 'LLC'",
|
||||
Example: "Inc",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return companySuffix(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("bs", Info{
|
||||
Display: "BS",
|
||||
Category: "company",
|
||||
Description: "Random bs company word",
|
||||
Example: "front-end",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return bs(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("blurb", Info{
|
||||
Display: "Blurb",
|
||||
Category: "company",
|
||||
Description: "Brief description or summary of a company's purpose, products, or services",
|
||||
Example: "word",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return blurb(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("buzzword", Info{
|
||||
Display: "Buzzword",
|
||||
Category: "company",
|
||||
Description: "Trendy or overused term often used in business to sound impressive",
|
||||
Example: "disintermediate",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return buzzWord(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("job", Info{
|
||||
Display: "Job",
|
||||
Category: "company",
|
||||
Description: "Position or role in employment, involving specific tasks and responsibilities",
|
||||
Example: `{
|
||||
"company": "ClearHealthCosts",
|
||||
"title": "Agent",
|
||||
"descriptor": "Future",
|
||||
"level": "Tactics"
|
||||
}`,
|
||||
Output: "map[string]string",
|
||||
ContentType: "application/json",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return job(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("jobtitle", Info{
|
||||
Display: "Job Title",
|
||||
Category: "company",
|
||||
Description: "Specific title for a position or role within a company or organization",
|
||||
Example: "Director",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return jobTitle(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("jobdescriptor", Info{
|
||||
Display: "Job Descriptor",
|
||||
Category: "company",
|
||||
Description: "Word used to describe the duties, requirements, and nature of a job",
|
||||
Example: "Central",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return jobDescriptor(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("joblevel", Info{
|
||||
Display: "Job Level",
|
||||
Category: "company",
|
||||
Description: "Random job level",
|
||||
Example: "Assurance",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return jobLevel(f), nil
|
||||
},
|
||||
})
|
||||
|
||||
AddFuncLookup("slogan", Info{
|
||||
Display: "Slogan",
|
||||
Category: "company",
|
||||
Description: "Catchphrase or motto used by a company to represent its brand or values",
|
||||
Example: "Universal seamless Focus, interactive.",
|
||||
Output: "string",
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
return slogan(f), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
package gofakeit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CSVOptions defines values needed for csv generation
|
||||
type CSVOptions struct {
|
||||
Delimiter string `json:"delimiter" xml:"delimiter" fake:"{randomstring:[,,tab]}"`
|
||||
RowCount int `json:"row_count" xml:"row_count" fake:"{number:1,10}"`
|
||||
Fields []Field `json:"fields" xml:"fields" fake:"{fields}"`
|
||||
}
|
||||
|
||||
// CSV generates an object or an array of objects in json format
|
||||
// A nil CSVOptions returns a randomly structured CSV.
|
||||
func CSV(co *CSVOptions) ([]byte, error) { return csvFunc(GlobalFaker, co) }
|
||||
|
||||
// CSV generates an object or an array of objects in json format
|
||||
// A nil CSVOptions returns a randomly structured CSV.
|
||||
func (f *Faker) CSV(co *CSVOptions) ([]byte, error) { return csvFunc(f, co) }
|
||||
|
||||
func csvFunc(f *Faker, co *CSVOptions) ([]byte, error) {
|
||||
if co == nil {
|
||||
// We didn't get a CSVOptions, so create a new random one
|
||||
err := f.Struct(&co)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check delimiter
|
||||
if co.Delimiter == "" {
|
||||
co.Delimiter = ","
|
||||
}
|
||||
if strings.ToLower(co.Delimiter) == "tab" {
|
||||
co.Delimiter = "\t"
|
||||
}
|
||||
if co.Delimiter != "," && co.Delimiter != "\t" && co.Delimiter != ";" {
|
||||
return nil, errors.New("invalid delimiter type")
|
||||
}
|
||||
|
||||
// Check fields
|
||||
if co.Fields == nil || len(co.Fields) <= 0 {
|
||||
return nil, errors.New("must pass fields in order to build json object(s)")
|
||||
}
|
||||
|
||||
// Make sure you set a row count
|
||||
if co.RowCount <= 0 {
|
||||
return nil, errors.New("must have row count")
|
||||
}
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
w := csv.NewWriter(b)
|
||||
w.Comma = []rune(co.Delimiter)[0]
|
||||
|
||||
// Add header row
|
||||
header := make([]string, len(co.Fields))
|
||||
for i, field := range co.Fields {
|
||||
header[i] = field.Name
|
||||
}
|
||||
w.Write(header)
|
||||
|
||||
// Loop through row count +1(for header) and add fields
|
||||
for i := 1; i < co.RowCount+1; i++ {
|
||||
vr := make([]string, len(co.Fields))
|
||||
|
||||
// Loop through fields and add to them to map[string]any
|
||||
for ii, field := range co.Fields {
|
||||
if field.Function == "autoincrement" {
|
||||
vr[ii] = fmt.Sprintf("%d", i)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get function info
|
||||
funcInfo := GetFuncLookup(field.Function)
|
||||
if funcInfo == nil {
|
||||
return nil, errors.New("invalid function, " + field.Function + " does not exist")
|
||||
}
|
||||
|
||||
value, err := funcInfo.Generate(f, &field.Params, funcInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := value.([]byte); ok {
|
||||
// If it's a slice of bytes or struct, unmarshal it into an interface
|
||||
var v any
|
||||
if err := json.Unmarshal(value.([]byte), &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value = v
|
||||
}
|
||||
|
||||
// If the value is a list of possible values, marsha it into a string
|
||||
if reflect.TypeOf(value).Kind() == reflect.Struct ||
|
||||
reflect.TypeOf(value).Kind() == reflect.Ptr ||
|
||||
reflect.TypeOf(value).Kind() == reflect.Map ||
|
||||
reflect.TypeOf(value).Kind() == reflect.Slice {
|
||||
b, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value = string(b)
|
||||
}
|
||||
|
||||
vr[ii] = fmt.Sprintf("%v", value)
|
||||
}
|
||||
|
||||
w.Write(vr)
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
||||
if err := w.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func addFileCSVLookup() {
|
||||
AddFuncLookup("csv", Info{
|
||||
Display: "CSV",
|
||||
Category: "file",
|
||||
Description: "Individual lines or data entries within a CSV (Comma-Separated Values) format",
|
||||
Example: `id,first_name,last_name,password
|
||||
1,Markus,Moen,Dc0VYXjkWABx
|
||||
2,Osborne,Hilll,XPJ9OVNbs5lm`,
|
||||
Output: "[]byte",
|
||||
ContentType: "text/csv",
|
||||
Params: []Param{
|
||||
{Field: "delimiter", Display: "Delimiter", Type: "string", Default: ",", Description: "Separator in between row values"},
|
||||
{Field: "rowcount", Display: "Row Count", Type: "int", Default: "100", Description: "Number of rows"},
|
||||
{Field: "fields", Display: "Fields", Type: "[]Field", Description: "Fields containing key name and function"},
|
||||
},
|
||||
Generate: func(f *Faker, m *MapParams, info *Info) (any, error) {
|
||||
co := CSVOptions{}
|
||||
|
||||
delimiter, err := info.GetString(m, "delimiter")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
co.Delimiter = delimiter
|
||||
|
||||
rowcount, err := info.GetInt(m, "rowcount")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
co.RowCount = rowcount
|
||||
|
||||
fieldsStr, err := info.GetStringArray(m, "fields")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check to make sure fields has length
|
||||
if len(fieldsStr) > 0 {
|
||||
co.Fields = make([]Field, len(fieldsStr))
|
||||
|
||||
for i, f := range fieldsStr {
|
||||
// Unmarshal fields string into fields array
|
||||
err = json.Unmarshal([]byte(f), &co.Fields[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
csvOut, err := csvFunc(f, &co)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return csvOut, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# Gofakeit Data
|
||||
|
||||
Gofakeit data set
|
||||
|
||||
## List
|
||||
|
||||
```go
|
||||
List()
|
||||
```
|
||||
|
||||
## Get/Set/Remove Data
|
||||
|
||||
```go
|
||||
data.Get("desserts")
|
||||
|
||||
data.Set("desserts", map[string][]string{
|
||||
"cake": {"chocolate", "vanilla"},
|
||||
"pie": {"apple", "pecan"},
|
||||
"ice cream": {"strawberry", "vanilla"},
|
||||
})
|
||||
|
||||
data.Remove("desserts")
|
||||
```
|
||||
|
||||
## Get/Set/Remove Sub Data
|
||||
|
||||
```go
|
||||
data.GetSubData("desserts", "cake")
|
||||
|
||||
data.SetSub("desserts", "cake", []string{"chocolate", "vanilla"})
|
||||
|
||||
data.RemoveSub("desserts", "cake")
|
||||
```
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package data
|
||||
|
||||
// Address consists of address information
|
||||
var Address = map[string][]string{
|
||||
"number": {"#####", "####", "###"},
|
||||
"street_prefix": {"North", "East", "West", "South", "New", "Lake", "Port"},
|
||||
"street_name": {"Alley", "Avenue", "Branch", "Bridge", "Brook", "Brooks", "Burg", "Burgs", "Bypass", "Camp", "Canyon", "Cape", "Causeway", "Center", "Centers", "Circle", "Circles", "Cliff", "Cliffs", "Club", "Common", "Corner", "Corners", "Course", "Court", "Courts", "Cove", "Coves", "Creek", "Crescent", "Crest", "Crossing", "Crossroad", "Curve", "Dale", "Dam", "Divide", "Drive", "Drive", "Drives", "Estate", "Estates", "Expressway", "Extension", "Extensions", "Fall", "Falls", "Ferry", "Field", "Fields", "Flat", "Flats", "Ford", "Fords", "Forest", "Forge", "Forges", "Fork", "Forks", "Fort", "Freeway", "Garden", "Gardens", "Gateway", "Glen", "Glens", "Green", "Greens", "Grove", "Groves", "Harbor", "Harbors", "Haven", "Heights", "Highway", "Hill", "Hills", "Hollow", "Inlet", "Inlet", "Island", "Island", "Islands", "Islands", "Isle", "Isle", "Junction", "Junctions", "Key", "Keys", "Knoll", "Knolls", "Lake", "Lakes", "Land", "Landing", "Lane", "Light", "Lights", "Loaf", "Lock", "Locks", "Locks", "Lodge", "Lodge", "Loop", "Mall", "Manor", "Manors", "Meadow", "Meadows", "Mews", "Mill", "Mills", "Mission", "Mission", "Motorway", "Mount", "Mountain", "Mountain", "Mountains", "Mountains", "Neck", "Orchard", "Oval", "Overpass", "Park", "Parks", "Parkway", "Parkways", "Pass", "Passage", "Path", "Pike", "Pine", "Pines", "Place", "Plain", "Plains", "Plains", "Plaza", "Plaza", "Point", "Points", "Port", "Port", "Ports", "Ports", "Prairie", "Prairie", "Radial", "Ramp", "Ranch", "Rapid", "Rapids", "Rest", "Ridge", "Ridges", "River", "Road", "Road", "Roads", "Roads", "Route", "Row", "Rue", "Run", "Shoal", "Shoals", "Shore", "Shores", "Skyway", "Spring", "Springs", "Springs", "Spur", "Spurs", "Square", "Square", "Squares", "Squares", "Station", "Station", "Stravenue", "Stravenue", "Stream", "Stream", "Street", "Street", "Streets", "Summit", "Summit", "Terrace", "Throughway", "Trace", "Track", "Trafficway", "Trail", "Trail", "Tunnel", "Tunnel", "Turnpike", "Turnpike", "Underpass", "Union", "Unions", "Valley", "Valleys", "Via", "Viaduct", "View", "Views", "Village", "Village", "Villages", "Ville", "Vista", "Vista", "Walk", "Walks", "Wall", "Way", "Ways", "Well", "Wells"},
|
||||
"street_suffix": {"town", "ton", "land", "ville", "berg", "burgh", "borough", "bury", "view", "port", "mouth", "stad", "furt", "chester", "mouth", "fort", "haven", "side", "shire"},
|
||||
"city": {"New York City", "Los Angeles", "Chicago", "Houston", "Philadelphia", "Phoenix", "San Antonio", "San Diego", "Dallas", "San Jose", "Austin", "Jacksonville", "Indianapolis", "San Francisco", "Columbus", "Fort Worth", "Charlotte", "Detroit", "El Paso", "Memphis", "Boston", "Seattle", "Denver", "Washington", "Nashville-Davidson", "Baltimore", "Louisville/Jefferson", "Portland", "Oklahoma", "Milwaukee", "Las Vegas", "Albuquerque", "Tucson", "Fresno", "Sacramento", "Long Beach", "Kansas", "Mesa", "Virginia Beach", "Atlanta", "Colorado Springs", "Raleigh", "Omaha", "Miami", "Oakland", "Tulsa", "Minneapolis", "Cleveland", "Wichita", "Arlington", "New Orleans", "Bakersfield", "Tampa", "Honolulu", "Anaheim", "Aurora", "Santa Ana", "St. Louis", "Riverside", "Corpus Christi", "Pittsburgh", "Lexington-Fayette", "Stockton", "Cincinnati", "St. Paul", "Toledo", "Newark", "Greensboro", "Plano", "Henderson", "Lincoln", "Buffalo", "Fort Wayne", "Jersey", "Chula Vista", "Orlando", "St. Petersburg", "Norfolk", "Chandler", "Laredo", "Madison", "Durham", "Lubbock", "Winston-Salem", "Garland", "Glendale", "Hialeah", "Reno", "Baton Rouge", "Irvine", "Chesapeake", "Irving", "Scottsdale", "North Las Vegas", "Fremont", "San Bernardino", "Boise", "Birmingham"},
|
||||
"state": {"Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"},
|
||||
"state_abr": {"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY", "AE", "AA", "AP"},
|
||||
"zip": {"#####"},
|
||||
"country": {"Andorra", "United Arab Emirates", "Afghanistan", "Antigua and Barbuda", "Anguilla", "Albania", "Armenia", "Angola", "Antarctica", "Argentina", "American Samoa", "Austria", "Australia", "Aruba", "Åland Islands", "Azerbaijan", "Bosnia and Herzegovina", "Barbados", "Bangladesh", "Belgium", "Burkina Faso", "Bulgaria", "Bahrain", "Burundi", "Benin", "Saint Barthélemy", "Bermuda", "Brunei Darussalam", "Bolivia (Plurinational State of)", "Bonaire, Sint Eustatius and Saba", "Brazil", "Bahamas", "Bhutan", "Bouvet Island", "Botswana", "Belarus", "Belize", "Canada", "Cocos (Keeling) Islands", "Congo, Democratic Republic of the", "Central African Republic", "Congo", "Switzerland", "Côte d'Ivoire", "Cook Islands", "Chile", "Cameroon", "China", "Colombia", "Costa Rica", "Cuba", "Cabo Verde", "Curaçao", "Christmas Island", "Cyprus", "Czechia", "Germany", "Djibouti", "Denmark", "Dominica", "Dominican Republic", "Algeria", "Ecuador", "Estonia", "Egypt", "Western Sahara", "Eritrea", "Spain", "Ethiopia", "Finland", "Fiji", "Falkland Islands (Malvinas)", "Micronesia (Federated States of)", "Faroe Islands", "France", "Gabon", "United Kingdom of Great Britain and Northern Ireland", "Grenada", "Georgia", "French Guiana", "Guernsey", "Ghana", "Gibraltar", "Greenland", "Gambia", "Guinea", "Guadeloupe", "Equatorial Guinea", "Greece", "South Georgia and the South Sandwich Islands", "Guatemala", "Guam", "Guinea-Bissau", "Guyana", "Hong Kong", "Heard Island and McDonald Islands", "Honduras", "Croatia", "Haiti", "Hungary", "Indonesia", "Ireland", "Israel", "Isle of Man", "India", "British Indian Ocean Territory", "Iraq", "Iran (Islamic Republic of)", "Iceland", "Italy", "Jersey", "Jamaica", "Jordan", "Japan", "Kenya", "Kyrgyzstan", "Cambodia", "Kiribati", "Comoros", "Saint Kitts and Nevis", "Korea (Democratic People's Republic of)", "Korea, Republic of", "Kuwait", "Cayman Islands", "Kazakhstan", "Lao People's Democratic Republic", "Lebanon", "Saint Lucia", "Liechtenstein", "Sri Lanka", "Liberia", "Lesotho", "Lithuania", "Luxembourg", "Latvia", "Libya", "Morocco", "Monaco", "Moldova, Republic of", "Montenegro", "Saint Martin (French part)", "Madagascar", "Marshall Islands", "North Macedonia", "Mali", "Myanmar", "Mongolia", "Macao", "Northern Mariana Islands", "Martinique", "Mauritania", "Montserrat", "Malta", "Mauritius", "Maldives", "Malawi", "Mexico", "Malaysia", "Mozambique", "Namibia", "New Caledonia", "Niger", "Norfolk Island", "Nigeria", "Nicaragua", "Netherlands", "Norway", "Nepal", "Nauru", "Niue", "New Zealand", "Oman", "Panama", "Peru", "French Polynesia", "Papua New Guinea", "Philippines", "Pakistan", "Poland", "Saint Pierre and Miquelon", "Pitcairn", "Puerto Rico", "Palestine, State of", "Portugal", "Palau", "Paraguay", "Qatar", "Réunion", "Romania", "Serbia", "Russian Federation", "Rwanda", "Saudi Arabia", "Solomon Islands", "Seychelles", "Sudan", "Sweden", "Singapore", "Saint Helena, Ascension and Tristan da Cunha", "Slovenia", "Svalbard and Jan Mayen", "Slovakia", "Sierra Leone", "San Marino", "Senegal", "Somalia", "Suriname", "South Sudan", "Sao Tome and Principe", "El Salvador", "Sint Maarten (Dutch part)", "Syrian Arab Republic", "Eswatini", "Turks and Caicos Islands", "Chad", "French Southern Territories", "Togo", "Thailand", "Tajikistan", "Tokelau", "Timor-Leste", "Turkmenistan", "Tunisia", "Tonga", "Turkey", "Trinidad and Tobago", "Tuvalu", "Taiwan, Province of China", "Tanzania, United Republic of", "Ukraine", "Uganda", "United States Minor Outlying Islands", "United States of America", "Uruguay", "Uzbekistan", "Holy See", "Saint Vincent and the Grenadines", "Venezuela (Bolivarian Republic of)", "Virgin Islands (British)", "Virgin Islands (U.S.)", "Viet Nam", "Vanuatu", "Wallis and Futuna", "Samoa", "Yemen", "Mayotte", "South Africa", "Zambia", "Zimbabwe"},
|
||||
"country_abr": {"AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "ZA", "ZM", "ZW"},
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package data
|
||||
|
||||
// Animal consists of animal information
|
||||
var Animal = map[string][]string{
|
||||
"petname": {"Alfalfa", "Archie", "Attila", "Baloo", "Bark Twain", "Barney", "Beans", "Bernadette", "Betty", "Binx", "Biscuit", "Bitsy", "Bob", "Bruiser", "Butterball", "Butters", "Chalupa", "Cheeseburger", "Chewbarka", "Chompers", "Cujo", "Demi", "Dobby", "Doc McDoggins", "Droolius Caesar", "Elmo", "Fergus", "Fluffernutter", "Franz Fur-dinand", "Frodo", "Fyodor Dogstoevsky", "Gary", "Gollum", "Hairy Paw-ter", "Hercules", "Hobbit", "Jabba", "Jellybean", "Jimmy Chew", "Kareem Abdul Ja-Bark", "Kevin", "Khaleesi", "Larry", "Lloyd", "Mary Puppins", "Matilda", "Meatball", "Mister Miyagi", "Moose", "Munchkin", "Nacho", "Noodles", "Nugget", "Olga", "Orville Redenbarker", "Ozzy Pawsborne", "Pam", "Peanut", "Pee Wee", "Pikachu", "Prince of Barkness", "Pumba", "Rambo", "Rex", "Rocky", "Rufus", "Salsa", "Salvador Dogi", "Santa Paws", "Sarah Jessica Barker", "Scrappy", "Sherlock Bones", "Squeakers", "Squirt", "Tank", "Tater", "The Notorious D.O.G.", "Toto", "Twinkie", "Waffles", "Waldo", "Winnie the Poodle", "Woofgang Puck", "Yoda", "Zeus"},
|
||||
"animal": {"alligator", "alpaca", "ant", "antelope", "ape", "armadillo", "baboon", "badger", "bat", "bear", "beaver", "bee", "beetle", "buffalo", "butterfly", "camel", "caribou", "cat", "cattle", "cheetah", "chimpanzee", "chinchilla", "cicada", "clam", "cockroach", "cod", "coyote", "crab", "cricket", "crocodile", "crow", "deer", "dinosaur", "dog", "dolphin", "donkey", "duck", "eagle", "eel", "elephant", "elk", "ferret", "fish", "fly", "fox", "frog", "gerbil", "giraffe", "gnat", "gnu", "goat", "goldfish", "goose", "gorilla", "grasshopper", "guinea pig", "hamster", "hare", "hedgehog", "herring", "hippopotamus", "hornet", "horse", "hound", "hyena", "impala", "jackal", "jellyfish", "kangaroo", "koala", "leopard", "lion", "lizard", "llama", "locust", "louse", "macaw", "mallard", "mammoth", "manatee", "marten", "mink", "minnow", "mole", "monkey", "moose", "mosquito", "mouse", "mule", "muskrat", "otter", "ox", "oyster", "panda", "pig", "platypus", "porcupine", "porpoise", "prairie dog", "pug", "rabbit", "raccoon", "rat", "raven", "reindeer", "rhinoceros", "salmon", "sardine", "scorpion", "sea lion", "seal", "serval", "shark", "sheep", "skunk", "snail", "snake", "spider", "squirrel", "swan", "termite", "tiger", "toad", "tortoise", "trout", "turtle", "wallaby", "walrus", "wasp", "water buffalo", "weasel", "whale", "wildebeest", "wolf", "wombat", "woodchuck", "worm", "yak", "yellowjacket", "zebra"},
|
||||
"type": {"amphibians", "birds", "fish", "invertebrates", "mammals", "reptiles"},
|
||||
"farm": {"Chicken", "Cow", "Donkey", "Duck", "Goat", "Goose", "Horse", "Llama", "Pig", "Sheep", "Turkey"},
|
||||
"cat": {"Abyssinian", "Aegean", "American Bobtail", "American Curl", "American Shorthair", "American Wirehair", "Arabian Mau", "Asian", "Asian Semi-longhair", "Australian Mist", "Balinese", "Bambino", "Bengal", "Birman", "Bombay", "Brazilian Shorthair", "British Longhair", "British Semipi-longhair", "British Shorthair", "Burmese", "Burmilla", "California Spangled", "Chantilly-Tiffany", "Chartreux", "Chausie", "Cheetoh", "Colorpoint Shorthair", "Cornish Rex", "Cymric, or Manx Longhair", "Cyprus", "Devon Rex", "Donskoy, or Don Sphynx", "Dragon Li", "Dwarf cat, or Dwelf", "Egyptian Mau", "European Shorthair", "Exotic Shorthair", "Foldex Cat", "German Rex", "Havana Brown", "Highlander", "Himalayan, or Colorpoint Persian", "Japanese Bobtail", "Javanese", "Khao Manee", "Korat", "Korean Bobtail", "Korn Ja", "Kurilian Bobtail", "Kurilian Bobtail, or Kuril Islands Bobtail", "LaPerm", "Lykoi", "Maine Coon", "Manx", "Mekong Bobtail", "Minskin", "Munchkin", "Napoleon", "Nebelung", "Norwegian Forest Cat", "Ocicat", "Ojos Azules", "Oregon Rex", "Oriental Bicolor", "Oriental Longhair", "Oriental Shorthair", "Persian", "Peterbald", "Pixie-bob", "Raas", "Ragamuffin", "Ragdoll", "Russian Blue", "Russian White, Black and Tabby", "Sam Sawet", "Savannah", "Scottish Fold", "Selkirk Rex", "Serengeti", "Serrade petit", "Siamese", "Siberian", "Singapura", "Snowshoe", "Sokoke", "Somali", "Sphynx", "Suphalak", "Thai", "Tonkinese", "Toyger", "Turkish Angora", "Turkish Van", "Ukrainian Levkoy"},
|
||||
"dog": {"Affenpinscher", "African", "Airedale", "Akita", "Appenzeller", "Basenji", "Beagle", "Bluetick", "Borzoi", "Bouvier", "Boxer", "Brabancon", "Briard", "Boston Bulldog", "French Bulldog", "Staffordshire Bullterrier", "Cairn", "Chihuahua", "Chow", "Clumber", "Border Collie", "Coonhound", "Cardigan Corgi", "Dachshund", "Great Dane", "Scottish Deerhound", "Dhole", "Dingo", "Doberman", "Norwegian Elkhound", "Entlebucher", "Eskimo", "Germanshepherd", "Italian Greyhound", "Groenendael", "Ibizan Hound", "Afghan Hound", "Basset Hound", "Blood Hound", "English Hound", "Walker Hound", "Husky", "Keeshond", "Kelpie", "Komondor", "Kuvasz", "Labrador", "Leonberg", "Lhasa", "Malamute", "Malinois", "Maltese", "Bull Mastiff", "Tibetan Mastiff", "Mexicanhairless", "Bernese Mountain", "Swiss Mountain", "Newfoundland", "Otterhound", "Papillon", "Pekinese", "Pembroke", "Miniature Pinscher", "German Pointer", "Pomeranian", "Miniature Poodle", "Standard Poodle", "Toy Poodle", "Pug", "Pyrenees", "Redbone", "Chesapeake Retriever", "Curly Retriever", "Flatcoated Retriever", "Golden Retriever", "Rhodesian Ridgeback", "Rottweiler", "Saluki", "Samoyed", "Schipperke", "Giant Schnauzer", "Miniature Schnauzer", "English Setter", "Gordon Setter", "Irish Setter", "English Sheepdog", "Shetland Sheepdog", "Shiba", "Shihtzu", "Blenheim Spaniel", "Brittany Spaniel", "Cocker Spaniel", "Irish Spaniel", "Japanese Spaniel", "Sussex Spaniel", "Welsh Spaniel", "English Springer", "Stbernard", "American Terrier", "Australian Terrier", "Bedlington Terrier", "Border Terrier", "Dandie Terrier", "Fox Terrier", "Irish Terrier", "Kerryblue Terrier", "Lakeland Terrier", "Norfolk Terrier", "Norwich Terrier", "Patterdale Terrier", "Rat Terrier", "Scottish Terrier", "Sealyham Terrier", "Silky Terrier", "Tibetan Terrier", "Toy Terrier", "Westhighland Terrier", "Wheaten Terrier", "Yorkshire Terrier", "Vizsla", "Weimaraner", "Whippet", "Irish Wolfhound"},
|
||||
"bird": {"albatross", "bluejay", "canary", "cardinal", "chicken", "crow", "dove", "duck", "eagle", "emu", "falcon", "flamingo", "goose", "hornbill", "hummingbird", "ibis", "jay", "kingfisher", "lovebird", "mynah", "nightingale", "oriole", "ostrich", "owl", "parrot", "peacock", "penguin", "quail", "rooster", "sparrow", "swan", "thrush", "toucan", "vulture", "woodpecker", "yellow warbler"},
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package data
|
||||
|
||||
// Beer consists of various beer information
|
||||
var Beer = map[string][]string{
|
||||
"name": {"Pliny The Elder", "Founders Kentucky Breakfast", "Trappistes Rochefort 10", "HopSlam Ale", "Stone Imperial Russian Stout", "St. Bernardus Abt 12", "Founders Breakfast Stout", "Weihenstephaner Hefeweissbier", "Péché Mortel", "Celebrator Doppelbock", "Duvel", "Dreadnaught IPA", "Nugget Nectar", "La Fin Du Monde", "Bourbon County Stout", "Old Rasputin Russian Imperial Stout", "Two Hearted Ale", "Ruination IPA", "Schneider Aventinus", "Double Bastard Ale", "90 Minute IPA", "Hop Rod Rye", "Trappistes Rochefort 8", "Chimay Grande Réserve", "Stone IPA", "Arrogant Bastard Ale", "Edmund Fitzgerald Porter", "Chocolate St", "Oak Aged Yeti Imperial Stout", "Ten FIDY", "Storm King Stout", "Shakespeare Oatmeal", "Alpha King Pale Ale", "Westmalle Trappist Tripel", "Samuel Smith’s Imperial IPA", "Yeti Imperial Stout", "Hennepin", "Samuel Smith’s Oatmeal Stout", "Brooklyn Black", "Oaked Arrogant Bastard Ale", "Sublimely Self-Righteous Ale", "Trois Pistoles", "Bell’s Expedition", "Sierra Nevada Celebration Ale", "Sierra Nevada Bigfoot Barleywine Style Ale", "Racer 5 India Pale Ale, Bear Republic Bre", "Orval Trappist Ale", "Hercules Double IPA", "Maharaj", "Maudite"},
|
||||
"hop": {"Ahtanum", "Amarillo", "Bitter Gold", "Bravo", "Brewer’s Gold", "Bullion", "Cascade", "Cashmere", "Centennial", "Chelan", "Chinook", "Citra", "Cluster", "Columbia", "Columbus", "Comet", "Crystal", "Equinox", "Eroica", "Fuggle", "Galena", "Glacier", "Golding", "Hallertau", "Horizon", "Liberty", "Magnum", "Millennium", "Mosaic", "Mt. Hood", "Mt. Rainier", "Newport", "Northern Brewer", "Nugget", "Olympic", "Palisade", "Perle", "Saaz", "Santiam", "Simcoe", "Sorachi Ace", "Sterling", "Summit", "Tahoma", "Tettnang", "TriplePearl", "Ultra", "Vanguard", "Warrior", "Willamette", "Yakima Gol"},
|
||||
"yeast": {"1007 - German Ale", "1010 - American Wheat", "1028 - London Ale", "1056 - American Ale", "1084 - Irish Ale", "1098 - British Ale", "1099 - Whitbread Ale", "1187 - Ringwood Ale", "1272 - American Ale II", "1275 - Thames Valley Ale", "1318 - London Ale III", "1332 - Northwest Ale", "1335 - British Ale II", "1450 - Dennys Favorite 50", "1469 - West Yorkshire Ale", "1728 - Scottish Ale", "1968 - London ESB Ale", "2565 - Kölsch", "1214 - Belgian Abbey", "1388 - Belgian Strong Ale", "1762 - Belgian Abbey II", "3056 - Bavarian Wheat Blend", "3068 - Weihenstephan Weizen", "3278 - Belgian Lambic Blend", "3333 - German Wheat", "3463 - Forbidden Fruit", "3522 - Belgian Ardennes", "3638 - Bavarian Wheat", "3711 - French Saison", "3724 - Belgian Saison", "3763 - Roeselare Ale Blend", "3787 - Trappist High Gravity", "3942 - Belgian Wheat", "3944 - Belgian Witbier", "2000 - Budvar Lager", "2001 - Urquell Lager", "2007 - Pilsen Lager", "2035 - American Lager", "2042 - Danish Lager", "2112 - California Lager", "2124 - Bohemian Lager", "2206 - Bavarian Lager", "2278 - Czech Pils", "2308 - Munich Lager", "2633 - Octoberfest Lager Blend", "5112 - Brettanomyces bruxellensis", "5335 - Lactobacillus", "5526 - Brettanomyces lambicus", "5733 - Pediococcus"},
|
||||
"malt": {"Black malt", "Caramel", "Carapils", "Chocolate", "Munich", "Caramel", "Carapils", "Chocolate malt", "Munich", "Pale", "Roasted barley", "Rye malt", "Special roast", "Victory", "Vienna", "Wheat mal"},
|
||||
"style": {"Light Lager", "Pilsner", "European Amber Lager", "Dark Lager", "Bock", "Light Hybrid Beer", "Amber Hybrid Beer", "English Pale Ale", "Scottish And Irish Ale", "Merican Ale", "English Brown Ale", "Porter", "Stout", "India Pale Ale", "German Wheat And Rye Beer", "Belgian And French Ale", "Sour Ale", "Belgian Strong Ale", "Strong Ale", "Fruit Beer", "Vegetable Beer", "Smoke-flavored", "Wood-aged Beer"},
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package data
|
||||
|
||||
var Books = map[string][]string{
|
||||
"title": {
|
||||
"Anna Karenina",
|
||||
"Beloved",
|
||||
"Blindness",
|
||||
"Bostan",
|
||||
"Buddenbrooks",
|
||||
"Crime and Punishment",
|
||||
"Don Quijote De La Mancha",
|
||||
"Fairy tales",
|
||||
"Faust",
|
||||
"Gulliver's Travels",
|
||||
"Gypsy Ballads",
|
||||
"Hamlet",
|
||||
"Harry potter and the sorcerer's stone",
|
||||
"King Lear",
|
||||
"Leaves of Grass",
|
||||
"Lolita",
|
||||
"Madame Bovary",
|
||||
"Memoirs of Hadrian",
|
||||
"Metamorphoses",
|
||||
"Moby Dick",
|
||||
"Nineteen Eighty-Four",
|
||||
"Odyssey",
|
||||
"Oedipus the King",
|
||||
"One Hundred Years of Solitude",
|
||||
"One Thousand and One Nights",
|
||||
"Othello",
|
||||
"Pippi Longstocking",
|
||||
"Pride and Prejudice",
|
||||
"Romeo & Juliet",
|
||||
"Sherlock Holmes",
|
||||
"Sons and Lovers",
|
||||
"The Adventures of Huckleberry Finn",
|
||||
"The Book Of Job",
|
||||
"The Brothers Karamazov",
|
||||
"The Golden Notebook",
|
||||
"The Idiot",
|
||||
"The Old Man and the Sea",
|
||||
"The Stranger",
|
||||
"Things Fall Apart",
|
||||
"Ulysses",
|
||||
"War and Peace",
|
||||
"Wuthering Heights",
|
||||
"Zorba the Greek",
|
||||
},
|
||||
"author": {
|
||||
"Albert Camus",
|
||||
"Astrid Lindgren",
|
||||
"Charles Dickens",
|
||||
"D. H. Lawrence",
|
||||
"Edgar Allan Poe",
|
||||
"Emily Brontë",
|
||||
"Ernest Hemingway",
|
||||
"Franz Kafka",
|
||||
"Fyodor Dostoevsky",
|
||||
"George Orwell",
|
||||
"Hans Christian Andersen",
|
||||
"Homer",
|
||||
"James Joyce",
|
||||
"Jane Austen",
|
||||
"Johann Wolfgang von Goethe",
|
||||
"Jorge Luis Borges",
|
||||
"Joanne K. Rowling",
|
||||
"Leo Tolstoy",
|
||||
"Marcel Proust",
|
||||
"Mark Twain",
|
||||
"Paul Celan",
|
||||
"Salman Rushdie",
|
||||
"Sophocles",
|
||||
"Thomas Mann",
|
||||
"Toni Morrison",
|
||||
"Vladimir Nabokov",
|
||||
"William Faulkner",
|
||||
"William Shakespeare",
|
||||
"Yasunari Kawabata",
|
||||
},
|
||||
"genre": {
|
||||
"Adventure",
|
||||
"Comic",
|
||||
"Crime",
|
||||
"Erotic",
|
||||
"Fiction",
|
||||
"Fantasy",
|
||||
"Historical",
|
||||
"Horror",
|
||||
"Magic",
|
||||
"Mystery",
|
||||
"Philosophical",
|
||||
"Political",
|
||||
"Romance",
|
||||
"Saga",
|
||||
"Satire",
|
||||
"Science",
|
||||
"Speculative",
|
||||
"Thriller",
|
||||
"Urban",
|
||||
},
|
||||
}
|
||||
+10
File diff suppressed because one or more lines are too long
+7
@@ -0,0 +1,7 @@
|
||||
package data
|
||||
|
||||
var Celebrity = map[string][]string{
|
||||
"actor": {"Arnold Schwarzenegger", "Jim Carrey", "Emma Watson", "Robert Downey Jr.", "Daniel Radcliffe", "Chris Evans", "Leonardo DiCaprio", "Tom Cruise", "Brad Pitt", "Charles Chaplin", "Morgan Freeman", "Tom Hanks", "Hugh Jackman", "Matt Damon", "Sylvester Stallone", "Will Smith", "Clint Eastwood", "Cameron Diaz", "George Clooney", "Steven Spielberg", "Harrison Ford", "Robert De Niro", "Al Pacino", "Robert Downey Jr.", "Russell Crowe", "Liam Neeson", "Kate Winslet", "Mark Wahlberg", "Natalie Portman", "Pierce Brosnan", "Sean Connery", "Orlando Bloom", "Dwayne Johnson", "Jackie Chan", "Angelina Jolie", "Adam Sandler", "Scarlett Johansson", "Heath Ledger", "Anne Hathaway", "Jessica Alba", "Edward Norton", "Keira Knightley", "Bradley Cooper", "Will Ferrell", "Julia Roberts", "Nicolas Cage", "Daniel Craig", "Keanu Reeves", "Ian McKellen", "Halle Berry", "Bruce Willis", "Ben Stiller", "Tommy Lee Jones", "Antonio Banderas", "Denzel Washington", "Steve Carell", "Shia LaBeouf", "Megan Fox", "James Franco", "Mel Gibson", "Vin Diesel", "Tim Allen", "Robin Williams", "Kevin Spacey", "Jason Biggs", "Seann William Scott", "Jean-Claude Van Damme", "Zach Galifianakis", "Owen Wilson", "Christian Bale", "Peter Jackson", "Sandra Bullock", "Bruce Lee", "Drew Barrymore", "Macaulay Culkin", "Jack Nicholson", "Bill Murray", "Sigourney Weaver", "Jake Gyllenhaal", "Jason Statham", "Jet Li", "Kate Beckinsale", "Rowan Atkinson", "Marlon Brando", "John Travolta", "Channing Tatum", "Ben Affleck", "Shah Rukh Khan", "Jennifer Aniston", "Emma Stone", "Chris Hemsworth", "James McAvoy", "James Cameron", "Amitabh Bachchan", "Brendan Fraser", "Rachel McAdams", "Tom Hiddleston", "Aamir Khan"},
|
||||
"business": {"Elon Musk", "Steve Jobs", "Jeff Bezos", "Bill Gates", "Mark Zuckerberg", "Sundar Pichai", "Walt Disney", "Warren Buffett", "Mukesh Ambani", "P. T. Barnum", "Colonel Sanders", "Ray Kroc", "Richard Branson", "Henry Ford", "Larry Page", "Steve Wozniak", "Ratan Tata", "John D. Rockefeller", "Madam C. J. Walker", "Tim Cook", "Andrew Carnegie", "Paul Allen", "Bobby Flay", "J. P. Morgan", "Satya Nadella", "Dhirubhai Ambani", "Carlos Slim", "Ross Perot", "Jamie Oliver", "Jack Ma", "Larry Ellison", "Sam Walton", "Sheryl Sandberg", "Marco Pierre White", "Indra Nooyi", "David Rockefeller", "Steve Ballmer", "Beyonce Knowles", "N. R. Narayana Murthy", "Mark Wahlberg", "Cameron Diaz", "Sergey Brin", "Howard Hughes", "Jessica Alba", "Dustin Moskovitz", "Eva Mendes", "Amancio Ortega Gaona", "Fred Trump", "Jamsetji Tata", "Kate Hudson", "Martha Stewart", "Peter Jones", "Marco Polo", "Susan Wojcicki", "Oskar Schindler", "Elizabeth Hurley", "Sean Combs", "Kate Spade", "Vincent McMahon", "David Chang", "Coco Chanel", "Vera Wang", "Arianna Huffington", "John McAfee", "Dany Garcia", "Richard Attenborough", "Donatella Versace", "Chris Hughes", "Alexis Ohanian", "J. Paul Getty", "Sharon Osbourne", "Bob Iger", "Kate Walsh", "Chris Gardner", "Jessica Simpson", "Guy Fieri", "Joy Mangano", "Wolfgang Puck", "Christie Brinkley", "Tom Steyer", "Evan Spiegel", "Hugh Hefner", "Preity Zinta", "Shane McMahon", "Salt Bae", "Mario Batali", "Bernard Arnault", "Michael Bloomberg", "Portia de Rossi", "Kevin O'Leary", "Roman Abramovich", "Jamie Dimon", "Rob Dyrdek", "Emeril Lagasse", "Kat Von D", "Karlie Kloss", "Antoni Porowski", "Edmond James de Rothschild", "Mitt Romney", "Aristotle Onassis", "Richard Benjamin Harrison", "Ben Bernanke", "Mark Cuban", "William Randolph Hearst", "Nate Robinson", "Alan Shepard", "Christina Anstead", "Laurene Powell Jobs", "Adam Weitsman", "Gladys Knight", "Gary Vaynerchuk", "Robert Kraft", "John Paul DeJoria", "Lori Greiner", "Carly Fiorina", "Lakshmi Mittal", "Jerry Jones", "Meg Whitman", "Azim Premji", "Lisa Vanderpump", "Dana White", "Russell Simmons", "Jennifer Flavin", "Harry Hamlin", "Conrad Hilton", "Prescott Bush", "Alvaro Morte", "Shigeru Miyamoto", "Phil Knight", "Jack Dorsey", "Barbara Bush", "Lee Iacocca", "Ma Huateng", "Rick Harrison", "Drew Scott", "Jawed Karim", "Daymond John", "Jaclyn Smith", "Maryse Ouellet", "Allegra Versace"},
|
||||
"sport": {"Pele", "Usain Bolt", "Muhammad Ali", "Carl Lewis", "Jesse Owens", "Sir Donald Bradman", "Billie Jean King", "Eddy Merckx", "Jackie Joyner-Kersee", "Lionel Messi", "Babe Didrikson Zaharias", "Michael Jordan", "Larisa Latynina", "Diego Maradona", "Serena Williams", "Babe Ruth", "Roger Federer", "Martina Navratilova", "Michael Phelps", "Lottie Dod", "Sachin Tendulkar", "Johan Cruyff", "Tiger Woods", "Sonja Henie", "Aryton Senna", "Nadia Comaneci", "Sergei Bubka", "Emil Zatopek", "Manny Pacquiao", "Imran Khan", "Jackie Robinson", "Shane Warne", "Dhyan Chand", "Fred Perry", "Lin Dan", "Abebe Bikila", "Clara Hughes", "Jan-Ove Waldner", "Bobby Moore", "Bjorn Borg", "Karch Kiraly", "Bradley Wiggins", "Seve Ballesteros", "David Beckham", "Michael Schumacher", "Greg Lemond", "Mia Hamm", "Jacques Anquetil", "Jack Nicklaus", "Steve Davis", "John McEnroe", "Monica Seles", "Magic Johnson", "Joe DiMaggio", "Roger Bannister", "Mo Farah", "Mark Spitz", "Chris Evert", "Al Oerter", "Jimmy Connors", "Michael Johnson", "Ian Botham", "Jim Thorpe", "Sir Steve Redgrave", "Steffi Graf", "Sebastian Coe", "Hicham El Guerrouj", "Eric Liddell", "W.G Grace", "Kenenisa Bekele", "Bernard Hinault", "Bob Beamon", "Paavo Nurmi", "David Campese", "Kelly Slater", "Haile Gebreselassie", "Rafael Nadal", "Brian Lara", "Chris Hoy", "Serge Blanco", "Cristiano Ronaldo", "Sir Gary Sobers", "Andy Murray", "George Best", "Sir Viv Richards", "Fausto Coppi", "Eusebio", "Rod Laver", "Grete Waitz", "Margaret Smith Court", "Tegla Laroupe", "Fanny Blankers-Koen", "Asbel Kiprop", "Lewis Hamilton", "C.B.Fry", "Annika Sörenstam", "Wilma Rudolph", "Alberta Tomba", "Bo Jackson"},
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package data
|
||||
|
||||
// Colors consists of color information
|
||||
var Colors = map[string][]string{
|
||||
"safe": {"black", "maroon", "green", "navy", "olive", "purple", "teal", "lime", "blue", "silver", "gray", "yellow", "fuchsia", "aqua", "white"},
|
||||
"full": {"AliceBlue", "AntiqueWhite", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "BlanchedAlmond", "Blue", "BlueViolet", "Brown", "BurlyWood", "CadetBlue", "Chartreuse", "Chocolate", "Coral", "CornflowerBlue", "Cornsilk", "Crimson", "Cyan", "DarkBlue", "DarkCyan", "DarkGoldenRod", "DarkGray", "DarkGreen", "DarkKhaki", "DarkMagenta", "DarkOliveGreen", "Darkorange", "DarkOrchid", "DarkRed", "DarkSalmon", "DarkSeaGreen", "DarkSlateBlue", "DarkSlateGray", "DarkTurquoise", "DarkViolet", "DeepPink", "DeepSkyBlue", "DimGray", "DimGrey", "DodgerBlue", "FireBrick", "FloralWhite", "ForestGreen", "Fuchsia", "Gainsboro", "GhostWhite", "Gold", "GoldenRod", "Gray", "Green", "GreenYellow", "HoneyDew", "HotPink", "IndianRed", "Indigo", "Ivory", "Khaki", "Lavender", "LavenderBlush", "LawnGreen", "LemonChiffon", "LightBlue", "LightCoral", "LightCyan", "LightGoldenRodYellow", "LightGray", "LightGreen", "LightPink", "LightSalmon", "LightSeaGreen", "LightSkyBlue", "LightSlateGray", "LightSteelBlue", "LightYellow", "Lime", "LimeGreen", "Linen", "Magenta", "Maroon", "MediumAquaMarine", "MediumBlue", "MediumOrchid", "MediumPurple", "MediumSeaGreen", "MediumSlateBlue", "MediumSpringGreen", "MediumTurquoise", "MediumVioletRed", "MidnightBlue", "MintCream", "MistyRose", "Moccasin", "NavajoWhite", "Navy", "OldLace", "Olive", "OliveDrab", "Orange", "OrangeRed", "Orchid", "PaleGoldenRod", "PaleGreen", "PaleTurquoise", "PaleVioletRed", "PapayaWhip", "PeachPuff", "Peru", "Pink", "Plum", "PowderBlue", "Purple", "Red", "RosyBrown", "RoyalBlue", "SaddleBrown", "Salmon", "SandyBrown", "SeaGreen", "SeaShell", "Sienna", "Silver", "SkyBlue", "SlateBlue", "SlateGray", "Snow", "SpringGreen", "SteelBlue", "Tan", "Teal", "Thistle", "Tomato", "Turquoise", "Violet", "Wheat", "White", "WhiteSmoke", "Yellow", "YellowGreen"},
|
||||
}
|
||||
|
||||
var ColorsNice = [][]string{
|
||||
{"#69d2e7", "#a7dbd8", "#e0e4cc", "#f38630", "#fa6900"},
|
||||
{"#fe4365", "#fc9d9a", "#f9cdad", "#c8c8a9", "#83af9b"},
|
||||
{"#ecd078", "#d95b43", "#c02942", "#542437", "#53777a"},
|
||||
{"#556270", "#4ecdc4", "#c7f464", "#ff6b6b", "#c44d58"},
|
||||
{"#774f38", "#e08e79", "#f1d4af", "#ece5ce", "#c5e0dc"},
|
||||
{"#e8ddcb", "#cdb380", "#036564", "#033649", "#031634"},
|
||||
{"#490a3d", "#bd1550", "#e97f02", "#f8ca00", "#8a9b0f"},
|
||||
{"#594f4f", "#547980", "#45ada8", "#9de0ad", "#e5fcc2"},
|
||||
{"#00a0b0", "#6a4a3c", "#cc333f", "#eb6841", "#edc951"},
|
||||
{"#e94e77", "#d68189", "#c6a49a", "#c6e5d9", "#f4ead5"},
|
||||
{"#3fb8af", "#7fc7af", "#dad8a7", "#ff9e9d", "#ff3d7f"},
|
||||
{"#d9ceb2", "#948c75", "#d5ded9", "#7a6a53", "#99b2b7"},
|
||||
{"#ffffff", "#cbe86b", "#f2e9e1", "#1c140d", "#cbe86b"},
|
||||
{"#efffcd", "#dce9be", "#555152", "#2e2633", "#99173c"},
|
||||
{"#343838", "#005f6b", "#008c9e", "#00b4cc", "#00dffc"},
|
||||
{"#413e4a", "#73626e", "#b38184", "#f0b49e", "#f7e4be"},
|
||||
{"#ff4e50", "#fc913a", "#f9d423", "#ede574", "#e1f5c4"},
|
||||
{"#99b898", "#fecea8", "#ff847c", "#e84a5f", "#2a363b"},
|
||||
{"#655643", "#80bca3", "#f6f7bd", "#e6ac27", "#bf4d28"},
|
||||
{"#00a8c6", "#40c0cb", "#f9f2e7", "#aee239", "#8fbe00"},
|
||||
{"#351330", "#424254", "#64908a", "#e8caa4", "#cc2a41"},
|
||||
{"#554236", "#f77825", "#d3ce3d", "#f1efa5", "#60b99a"},
|
||||
{"#5d4157", "#838689", "#a8caba", "#cad7b2", "#ebe3aa"},
|
||||
{"#8c2318", "#5e8c6a", "#88a65e", "#bfb35a", "#f2c45a"},
|
||||
{"#fad089", "#ff9c5b", "#f5634a", "#ed303c", "#3b8183"},
|
||||
{"#ff4242", "#f4fad2", "#d4ee5e", "#e1edb9", "#f0f2eb"},
|
||||
{"#f8b195", "#f67280", "#c06c84", "#6c5b7b", "#355c7d"},
|
||||
{"#d1e751", "#ffffff", "#000000", "#4dbce9", "#26ade4"},
|
||||
{"#1b676b", "#519548", "#88c425", "#bef202", "#eafde6"},
|
||||
{"#5e412f", "#fcebb6", "#78c0a8", "#f07818", "#f0a830"},
|
||||
{"#bcbdac", "#cfbe27", "#f27435", "#f02475", "#3b2d38"},
|
||||
{"#452632", "#91204d", "#e4844a", "#e8bf56", "#e2f7ce"},
|
||||
{"#eee6ab", "#c5bc8e", "#696758", "#45484b", "#36393b"},
|
||||
{"#f0d8a8", "#3d1c00", "#86b8b1", "#f2d694", "#fa2a00"},
|
||||
{"#2a044a", "#0b2e59", "#0d6759", "#7ab317", "#a0c55f"},
|
||||
{"#f04155", "#ff823a", "#f2f26f", "#fff7bd", "#95cfb7"},
|
||||
{"#b9d7d9", "#668284", "#2a2829", "#493736", "#7b3b3b"},
|
||||
{"#bbbb88", "#ccc68d", "#eedd99", "#eec290", "#eeaa88"},
|
||||
{"#b3cc57", "#ecf081", "#ffbe40", "#ef746f", "#ab3e5b"},
|
||||
{"#a3a948", "#edb92e", "#f85931", "#ce1836", "#009989"},
|
||||
{"#300030", "#480048", "#601848", "#c04848", "#f07241"},
|
||||
{"#67917a", "#170409", "#b8af03", "#ccbf82", "#e33258"},
|
||||
{"#aab3ab", "#c4cbb7", "#ebefc9", "#eee0b7", "#e8caaf"},
|
||||
{"#e8d5b7", "#0e2430", "#fc3a51", "#f5b349", "#e8d5b9"},
|
||||
{"#ab526b", "#bca297", "#c5ceae", "#f0e2a4", "#f4ebc3"},
|
||||
{"#607848", "#789048", "#c0d860", "#f0f0d8", "#604848"},
|
||||
{"#b6d8c0", "#c8d9bf", "#dadabd", "#ecdbbc", "#fedcba"},
|
||||
{"#a8e6ce", "#dcedc2", "#ffd3b5", "#ffaaa6", "#ff8c94"},
|
||||
{"#3e4147", "#fffedf", "#dfba69", "#5a2e2e", "#2a2c31"},
|
||||
{"#fc354c", "#29221f", "#13747d", "#0abfbc", "#fcf7c5"},
|
||||
{"#cc0c39", "#e6781e", "#c8cf02", "#f8fcc1", "#1693a7"},
|
||||
{"#1c2130", "#028f76", "#b3e099", "#ffeaad", "#d14334"},
|
||||
{"#a7c5bd", "#e5ddcb", "#eb7b59", "#cf4647", "#524656"},
|
||||
{"#dad6ca", "#1bb0ce", "#4f8699", "#6a5e72", "#563444"},
|
||||
{"#5c323e", "#a82743", "#e15e32", "#c0d23e", "#e5f04c"},
|
||||
{"#edebe6", "#d6e1c7", "#94c7b6", "#403b33", "#d3643b"},
|
||||
{"#fdf1cc", "#c6d6b8", "#987f69", "#e3ad40", "#fcd036"},
|
||||
{"#230f2b", "#f21d41", "#ebebbc", "#bce3c5", "#82b3ae"},
|
||||
{"#b9d3b0", "#81bda4", "#b28774", "#f88f79", "#f6aa93"},
|
||||
{"#3a111c", "#574951", "#83988e", "#bcdea5", "#e6f9bc"},
|
||||
{"#5e3929", "#cd8c52", "#b7d1a3", "#dee8be", "#fcf7d3"},
|
||||
{"#1c0113", "#6b0103", "#a30006", "#c21a01", "#f03c02"},
|
||||
{"#000000", "#9f111b", "#b11623", "#292c37", "#cccccc"},
|
||||
{"#382f32", "#ffeaf2", "#fcd9e5", "#fbc5d8", "#f1396d"},
|
||||
{"#e3dfba", "#c8d6bf", "#93ccc6", "#6cbdb5", "#1a1f1e"},
|
||||
{"#f6f6f6", "#e8e8e8", "#333333", "#990100", "#b90504"},
|
||||
{"#1b325f", "#9cc4e4", "#e9f2f9", "#3a89c9", "#f26c4f"},
|
||||
{"#a1dbb2", "#fee5ad", "#faca66", "#f7a541", "#f45d4c"},
|
||||
{"#c1b398", "#605951", "#fbeec2", "#61a6ab", "#accec0"},
|
||||
{"#5e9fa3", "#dcd1b4", "#fab87f", "#f87e7b", "#b05574"},
|
||||
{"#951f2b", "#f5f4d7", "#e0dfb1", "#a5a36c", "#535233"},
|
||||
{"#8dccad", "#988864", "#fea6a2", "#f9d6ac", "#ffe9af"},
|
||||
{"#2d2d29", "#215a6d", "#3ca2a2", "#92c7a3", "#dfece6"},
|
||||
{"#413d3d", "#040004", "#c8ff00", "#fa023c", "#4b000f"},
|
||||
{"#eff3cd", "#b2d5ba", "#61ada0", "#248f8d", "#605063"},
|
||||
{"#ffefd3", "#fffee4", "#d0ecea", "#9fd6d2", "#8b7a5e"},
|
||||
{"#cfffdd", "#b4dec1", "#5c5863", "#a85163", "#ff1f4c"},
|
||||
{"#9dc9ac", "#fffec7", "#f56218", "#ff9d2e", "#919167"},
|
||||
{"#4e395d", "#827085", "#8ebe94", "#ccfc8e", "#dc5b3e"},
|
||||
{"#a8a7a7", "#cc527a", "#e8175d", "#474747", "#363636"},
|
||||
{"#f8edd1", "#d88a8a", "#474843", "#9d9d93", "#c5cfc6"},
|
||||
{"#046d8b", "#309292", "#2fb8ac", "#93a42a", "#ecbe13"},
|
||||
{"#f38a8a", "#55443d", "#a0cab5", "#cde9ca", "#f1edd0"},
|
||||
{"#a70267", "#f10c49", "#fb6b41", "#f6d86b", "#339194"},
|
||||
{"#ff003c", "#ff8a00", "#fabe28", "#88c100", "#00c176"},
|
||||
{"#ffedbf", "#f7803c", "#f54828", "#2e0d23", "#f8e4c1"},
|
||||
{"#4e4d4a", "#353432", "#94ba65", "#2790b0", "#2b4e72"},
|
||||
{"#0ca5b0", "#4e3f30", "#fefeeb", "#f8f4e4", "#a5b3aa"},
|
||||
{"#4d3b3b", "#de6262", "#ffb88c", "#ffd0b3", "#f5e0d3"},
|
||||
{"#fffbb7", "#a6f6af", "#66b6ab", "#5b7c8d", "#4f2958"},
|
||||
{"#edf6ee", "#d1c089", "#b3204d", "#412e28", "#151101"},
|
||||
{"#9d7e79", "#ccac95", "#9a947c", "#748b83", "#5b756c"},
|
||||
{"#fcfef5", "#e9ffe1", "#cdcfb7", "#d6e6c3", "#fafbe3"},
|
||||
{"#9cddc8", "#bfd8ad", "#ddd9ab", "#f7af63", "#633d2e"},
|
||||
{"#30261c", "#403831", "#36544f", "#1f5f61", "#0b8185"},
|
||||
{"#aaff00", "#ffaa00", "#ff00aa", "#aa00ff", "#00aaff"},
|
||||
{"#d1313d", "#e5625c", "#f9bf76", "#8eb2c5", "#615375"},
|
||||
{"#ffe181", "#eee9e5", "#fad3b2", "#ffba7f", "#ff9c97"},
|
||||
{"#73c8a9", "#dee1b6", "#e1b866", "#bd5532", "#373b44"},
|
||||
{"#805841", "#dcf7f3", "#fffcdd", "#ffd8d8", "#f5a2a2"},
|
||||
}
|
||||
+10
File diff suppressed because one or more lines are too long
+8
@@ -0,0 +1,8 @@
|
||||
package data
|
||||
|
||||
// Computer consists of computer information
|
||||
var Computer = map[string][]string{
|
||||
"linux_processor": {"i686", "x86_64"},
|
||||
"mac_processor": {"Intel", "PPC", "U; Intel", "U; PPC"},
|
||||
"windows_platform": {"Windows NT 6.2", "Windows NT 6.1", "Windows NT 6.0", "Windows NT 5.2", "Windows NT 5.1", "Windows NT 5.01", "Windows NT 5.0", "Windows NT 4.0", "Windows 98; Win 9x 4.90", "Windows 98", "Windows 95", "Windows CE"},
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package data
|
||||
|
||||
// Currency consists of currency information
|
||||
var Currency = map[string][]string{
|
||||
"short": {"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BTN", "BWP", "BYR", "BZD", "CAD", "CDF", "CHF", "CLP", "CNY", "COP", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GGP", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "IMP", "INR", "IQD", "IRR", "ISK", "JEP", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SPL", "SRD", "STD", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TVD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VEF", "VND", "VUV", "WST", "XAF", "XCD", "XDR", "XOF", "XPF", "YER", "ZAR", "ZMW", "ZWD"},
|
||||
"long": {"United Arab Emirates Dirham", "Afghanistan Afghani", "Albania Lek", "Armenia Dram", "Netherlands Antilles Guilder", "Angola Kwanza", "Argentina Peso", "Australia Dollar", "Aruba Guilder", "Azerbaijan New Manat", "Bosnia and Herzegovina Convertible Marka", "Barbados Dollar", "Bangladesh Taka", "Bulgaria Lev", "Bahrain Dinar", "Burundi Franc", "Bermuda Dollar", "Brunei Darussalam Dollar", "Bolivia Boliviano", "Brazil Real", "Bahamas Dollar", "Bhutan Ngultrum", "Botswana Pula", "Belarus Ruble", "Belize Dollar", "Canada Dollar", "Congo/Kinshasa Franc", "Switzerland Franc", "Chile Peso", "China Yuan Renminbi", "Colombia Peso", "Costa Rica Colon", "Cuba Convertible Peso", "Cuba Peso", "Cape Verde Escudo", "Czech Republic Koruna", "Djibouti Franc", "Denmark Krone", "Dominican Republic Peso", "Algeria Dinar", "Egypt Pound", "Eritrea Nakfa", "Ethiopia Birr", "Euro Member Countries", "Fiji Dollar", "Falkland Islands (Malvinas) Pound", "United Kingdom Pound", "Georgia Lari", "Guernsey Pound", "Ghana Cedi", "Gibraltar Pound", "Gambia Dalasi", "Guinea Franc", "Guatemala Quetzal", "Guyana Dollar", "Hong Kong Dollar", "Honduras Lempira", "Croatia Kuna", "Haiti Gourde", "Hungary Forint", "Indonesia Rupiah", "Israel Shekel", "Isle of Man Pound", "India Rupee", "Iraq Dinar", "Iran Rial", "Iceland Krona", "Jersey Pound", "Jamaica Dollar", "Jordan Dinar", "Japan Yen", "Kenya Shilling", "Kyrgyzstan Som", "Cambodia Riel", "Comoros Franc", "Korea (North) Won", "Korea (South) Won", "Kuwait Dinar", "Cayman Islands Dollar", "Kazakhstan Tenge", "Laos Kip", "Lebanon Pound", "Sri Lanka Rupee", "Liberia Dollar", "Lesotho Loti", "Lithuania Litas", "Libya Dinar", "Morocco Dirham", "Moldova Leu", "Madagascar Ariary", "Macedonia Denar", "Myanmar (Burma) Kyat", "Mongolia Tughrik", "Macau Pataca", "Mauritania Ouguiya", "Mauritius Rupee", "Maldives (Maldive Islands) Rufiyaa", "Malawi Kwacha", "Mexico Peso", "Malaysia Ringgit", "Mozambique Metical", "Namibia Dollar", "Nigeria Naira", "Nicaragua Cordoba", "Norway Krone", "Nepal Rupee", "New Zealand Dollar", "Oman Rial", "Panama Balboa", "Peru Nuevo Sol", "Papua New Guinea Kina", "Philippines Peso", "Pakistan Rupee", "Poland Zloty", "Paraguay Guarani", "Qatar Riyal", "Romania New Leu", "Serbia Dinar", "Russia Ruble", "Rwanda Franc", "Saudi Arabia Riyal", "Solomon Islands Dollar", "Seychelles Rupee", "Sudan Pound", "Sweden Krona", "Singapore Dollar", "Saint Helena Pound", "Sierra Leone Leone", "Somalia Shilling", "Seborga Luigino", "Suriname Dollar", "São Tomé and Príncipe Dobra", "El Salvador Colon", "Syria Pound", "Swaziland Lilangeni", "Thailand Baht", "Tajikistan Somoni", "Turkmenistan Manat", "Tunisia Dinar", "Tonga Pa'anga", "Turkey Lira", "Trinidad and Tobago Dollar", "Tuvalu Dollar", "Taiwan New Dollar", "Tanzania Shilling", "Ukraine Hryvnia", "Uganda Shilling", "United States Dollar", "Uruguay Peso", "Uzbekistan Som", "Venezuela Bolivar", "Viet Nam Dong", "Vanuatu Vatu", "Samoa Tala", "Communauté Financière Africaine (BEAC) CFA Franc BEAC", "East Caribbean Dollar", "International Monetary Fund (IMF) Special Drawing Rights", "Communauté Financière Africaine (BCEAO) Franc", "Comptoirs Français du Pacifique (CFP) Franc", "Yemen Rial", "South Africa Rand", "Zambia Kwacha", "Zimbabwe Dollar"},
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package data
|
||||
|
||||
// Data consists of the main set of fake information
|
||||
var Data = map[string]map[string][]string{
|
||||
"person": Person,
|
||||
"address": Address,
|
||||
"company": Company,
|
||||
"job": Job,
|
||||
"lorem": Lorem,
|
||||
"language": Languages,
|
||||
"internet": Internet,
|
||||
"file": Files,
|
||||
"color": Colors,
|
||||
"computer": Computer,
|
||||
"hipster": Hipster,
|
||||
"beer": Beer,
|
||||
"hacker": Hacker,
|
||||
"animal": Animal,
|
||||
"currency": Currency,
|
||||
"log_level": LogLevels,
|
||||
"timezone": TimeZone,
|
||||
"car": Car,
|
||||
"emoji": Emoji,
|
||||
"word": Word,
|
||||
"sentence": Sentence,
|
||||
"food": Food,
|
||||
"minecraft": Minecraft,
|
||||
"celebrity": Celebrity,
|
||||
"error": Error,
|
||||
"html": Html,
|
||||
"book": Books,
|
||||
"movie": Movies,
|
||||
"school": School,
|
||||
"product": Product,
|
||||
}
|
||||
|
||||
func List() map[string][]string {
|
||||
var list = make(map[string][]string)
|
||||
|
||||
// Loop through the data and add the keys to the list
|
||||
for key := range Data {
|
||||
list[key] = []string{}
|
||||
|
||||
// Loop through the sub data and add the keys to the list
|
||||
for subkey := range Data[key] {
|
||||
list[key] = append(list[key], subkey)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func Get(key string) map[string][]string {
|
||||
// Make sure the key exists, if not return an empty map
|
||||
if _, ok := Data[key]; !ok {
|
||||
return make(map[string][]string)
|
||||
}
|
||||
|
||||
return Data[key]
|
||||
}
|
||||
|
||||
func Set(key string, data map[string][]string) {
|
||||
Data[key] = data
|
||||
}
|
||||
|
||||
func Remove(key string) {
|
||||
delete(Data, key)
|
||||
}
|
||||
|
||||
func GetSubData(key, subkey string) []string {
|
||||
// Make sure the key exists, if not return an empty map
|
||||
if _, ok := Data[key]; !ok {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return Data[key][subkey]
|
||||
}
|
||||
|
||||
func SetSub(key, subkey string, data []string) {
|
||||
// Make sure the key exists, if not add it
|
||||
if _, ok := Data[key]; !ok {
|
||||
Data[key] = make(map[string][]string)
|
||||
}
|
||||
|
||||
Data[key][subkey] = data
|
||||
}
|
||||
|
||||
func RemoveSub(key, subkey string) {
|
||||
delete(Data[key], subkey)
|
||||
}
|
||||
+10
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user