tile38/vendor/github.com/nats-io/jwt/exports.go

245 lines
6.9 KiB
Go

/*
* Copyright 2018-2019 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jwt
import (
"fmt"
"time"
)
// ResponseType is used to store an export response type
type ResponseType string
const (
// ResponseTypeSingleton is used for a service that sends a single response only
ResponseTypeSingleton = "Singleton"
// ResponseTypeStream is used for a service that will send multiple responses
ResponseTypeStream = "Stream"
// ResponseTypeChunked is used for a service that sends a single response in chunks (so not quite a stream)
ResponseTypeChunked = "Chunked"
)
// ServiceLatency is used when observing and exported service for
// latency measurements.
// Sampling 1-100, represents sampling rate, defaults to 100.
// Results is the subject where the latency metrics are published.
// A metric will be defined by the nats-server's ServiceLatency. Time durations
// are in nanoseconds.
// see https://github.com/nats-io/nats-server/blob/master/server/accounts.go#L524
// e.g.
// {
// "app": "dlc22",
// "start": "2019-09-16T21:46:23.636869585-07:00",
// "svc": 219732,
// "nats": {
// "req": 320415,
// "resp": 228268,
// "sys": 0
// },
// "total": 768415
// }
//
type ServiceLatency struct {
Sampling int `json:"sampling,omitempty"`
Results Subject `json:"results"`
}
func (sl *ServiceLatency) Validate(vr *ValidationResults) {
if sl.Sampling < 1 || sl.Sampling > 100 {
vr.AddError("sampling percentage needs to be between 1-100")
}
sl.Results.Validate(vr)
if sl.Results.HasWildCards() {
vr.AddError("results subject can not contain wildcards")
}
}
// Export represents a single export
type Export struct {
Name string `json:"name,omitempty"`
Subject Subject `json:"subject,omitempty"`
Type ExportType `json:"type,omitempty"`
TokenReq bool `json:"token_req,omitempty"`
Revocations RevocationList `json:"revocations,omitempty"`
ResponseType ResponseType `json:"response_type,omitempty"`
Latency *ServiceLatency `json:"service_latency,omitempty"`
}
// IsService returns true if an export is for a service
func (e *Export) IsService() bool {
return e.Type == Service
}
// IsStream returns true if an export is for a stream
func (e *Export) IsStream() bool {
return e.Type == Stream
}
// IsSingleResponse returns true if an export has a single response
// or no resopnse type is set, also checks that the type is service
func (e *Export) IsSingleResponse() bool {
return e.Type == Service && (e.ResponseType == ResponseTypeSingleton || e.ResponseType == "")
}
// IsChunkedResponse returns true if an export has a chunked response
func (e *Export) IsChunkedResponse() bool {
return e.Type == Service && e.ResponseType == ResponseTypeChunked
}
// IsStreamResponse returns true if an export has a chunked response
func (e *Export) IsStreamResponse() bool {
return e.Type == Service && e.ResponseType == ResponseTypeStream
}
// Validate appends validation issues to the passed in results list
func (e *Export) Validate(vr *ValidationResults) {
if e == nil {
vr.AddError("null export is not allowed")
return
}
if !e.IsService() && !e.IsStream() {
vr.AddError("invalid export type: %q", e.Type)
}
if e.IsService() && !e.IsSingleResponse() && !e.IsChunkedResponse() && !e.IsStreamResponse() {
vr.AddError("invalid response type for service: %q", e.ResponseType)
}
if e.IsStream() && e.ResponseType != "" {
vr.AddError("invalid response type for stream: %q", e.ResponseType)
}
if e.Latency != nil {
if !e.IsService() {
vr.AddError("latency tracking only permitted for services")
}
e.Latency.Validate(vr)
}
e.Subject.Validate(vr)
}
// Revoke enters a revocation by publickey using time.Now().
func (e *Export) Revoke(pubKey string) {
e.RevokeAt(pubKey, time.Now())
}
// RevokeAt enters a revocation by publickey and timestamp into this export
// If there is already a revocation for this public key that is newer, it is kept.
func (e *Export) RevokeAt(pubKey string, timestamp time.Time) {
if e.Revocations == nil {
e.Revocations = RevocationList{}
}
e.Revocations.Revoke(pubKey, timestamp)
}
// ClearRevocation removes any revocation for the public key
func (e *Export) ClearRevocation(pubKey string) {
e.Revocations.ClearRevocation(pubKey)
}
// IsRevokedAt checks if the public key is in the revoked list with a timestamp later than the one passed in.
// Generally this method is called with the subject and issue time of the jwt to be tested.
// DO NOT pass time.Now(), it will not produce a stable/expected response.
func (e *Export) IsRevokedAt(pubKey string, timestamp time.Time) bool {
return e.Revocations.IsRevoked(pubKey, timestamp)
}
// IsRevoked does not perform a valid check. Use IsRevokedAt instead.
func (e *Export) IsRevoked(_ string) bool {
return true
}
// Exports is a slice of exports
type Exports []*Export
// Add appends exports to the list
func (e *Exports) Add(i ...*Export) {
*e = append(*e, i...)
}
func isContainedIn(kind ExportType, subjects []Subject, vr *ValidationResults) {
m := make(map[string]string)
for i, ns := range subjects {
for j, s := range subjects {
if i == j {
continue
}
if ns.IsContainedIn(s) {
str := string(s)
_, ok := m[str]
if !ok {
m[str] = string(ns)
}
}
}
}
if len(m) != 0 {
for k, v := range m {
var vi ValidationIssue
vi.Blocking = true
vi.Description = fmt.Sprintf("%s export subject %q already exports %q", kind, k, v)
vr.Add(&vi)
}
}
}
// Validate calls validate on all of the exports
func (e *Exports) Validate(vr *ValidationResults) error {
var serviceSubjects []Subject
var streamSubjects []Subject
for _, v := range *e {
if v == nil {
vr.AddError("null export is not allowed")
continue
}
if v.IsService() {
serviceSubjects = append(serviceSubjects, v.Subject)
} else {
streamSubjects = append(streamSubjects, v.Subject)
}
v.Validate(vr)
}
isContainedIn(Service, serviceSubjects, vr)
isContainedIn(Stream, streamSubjects, vr)
return nil
}
// HasExportContainingSubject checks if the export list has an export with the provided subject
func (e *Exports) HasExportContainingSubject(subject Subject) bool {
for _, s := range *e {
if subject.IsContainedIn(s.Subject) {
return true
}
}
return false
}
func (e Exports) Len() int {
return len(e)
}
func (e Exports) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func (e Exports) Less(i, j int) bool {
return e[i].Subject < e[j].Subject
}