mirror of https://github.com/tidwall/tile38.git
245 lines
6.9 KiB
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
|
|
}
|