Update validator function for URIs. Change example of entering a CA to a

root cert generation. Other minor documentation updates. Fix private key
output in issue/sign.
This commit is contained in:
Jeff Mitchell 2015-11-19 11:32:18 -05:00
parent f41a2e562a
commit af3d6ced8e
18 changed files with 2220 additions and 63 deletions

5
Godeps/Godeps.json generated
View File

@ -13,6 +13,11 @@
"ImportPath": "github.com/armon/go-radix",
"Rev": "fbd82e84e2b13651f3abc5ffd26b65ba71bc8f93"
},
{
"ImportPath": "github.com/asaskevich/govalidator",
"Comment": "v2-37-gedd46cd",
"Rev": "edd46cdac249b001c7b7d88c6d43993ea875e8d8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws",
"Comment": "v0.10.4-18-gce51895",

View File

@ -0,0 +1,10 @@
language: go
go:
- 1.1
- 1.2
- 1.3
notifications:
email:
- bwatas@gmail.com

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Alex Saskevich
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.

View File

@ -0,0 +1,281 @@
govalidator
===========
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/asaskevich/govalidator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![GoDoc](https://godoc.org/github.com/asaskevich/govalidator?status.png)](https://godoc.org/github.com/asaskevich/govalidator) [![Coverage Status](https://img.shields.io/coveralls/asaskevich/govalidator.svg)](https://coveralls.io/r/asaskevich/govalidator?branch=master) [![views](https://sourcegraph.com/api/repos/github.com/asaskevich/govalidator/.counters/views.png)](https://sourcegraph.com/github.com/asaskevich/govalidator)
[![wercker status](https://app.wercker.com/status/1ec990b09ea86c910d5f08b0e02c6043/s "wercker status")](https://app.wercker.com/project/bykey/1ec990b09ea86c910d5f08b0e02c6043)
[![Build Status](https://travis-ci.org/asaskevich/govalidator.svg?branch=master)](https://travis-ci.org/asaskevich/govalidator)
A package of validators and sanitizers for strings, structs and collections. Based on [validator.js](https://github.com/chriso/validator.js).
#### Installation
Make sure that Go is installed on your computer.
Type the following command in your terminal:
go get github.com/asaskevich/govalidator
After it the package is ready to use.
#### Import package in your project
Add following line in your `*.go` file:
```go
import "github.com/asaskevich/govalidator"
```
If you unhappy to use long `govalidator`, you can do something like this:
```go
import (
valid "github.com/asaskevich/govalidator"
)
```
#### List of functions:
```go
func Abs(value float64) float64
func BlackList(str, chars string) string
func ByteLength(str string, params ...string) bool
func StringLength(str string, params ...string) bool
func CamelCaseToUnderscore(str string) string
func Contains(str, substring string) bool
func Count(array []interface{}, iterator ConditionIterator) int
func Each(array []interface{}, iterator Iterator)
func ErrorByField(e error, field string) string
func Filter(array []interface{}, iterator ConditionIterator) []interface{}
func Find(array []interface{}, iterator ConditionIterator) interface{}
func GetLine(s string, index int) (string, error)
func GetLines(s string) []string
func InRange(value, left, right float64) bool
func IsASCII(str string) bool
func IsAlpha(str string) bool
func IsAlphanumeric(str string) bool
func IsBase64(str string) bool
func IsByteLength(str string, min, max int) bool
func IsCreditCard(str string) bool
func IsDataURI(str string) bool
func IsDivisibleBy(str, num string) bool
func IsEmail(str string) bool
func IsFilePath(str string) (bool, int)
func IsFloat(str string) bool
func IsFullWidth(str string) bool
func IsHalfWidth(str string) bool
func IsHexadecimal(str string) bool
func IsHexcolor(str string) bool
func IsIP(str string) bool
func IsIPv4(str string) bool
func IsIPv6(str string) bool
func IsISBN(str string, version int) bool
func IsISBN10(str string) bool
func IsISBN13(str string) bool
func IsISO3166Alpha2(str string) bool
func IsISO3166Alpha3(str string) bool
func IsInt(str string) bool
func IsJSON(str string) bool
func IsLatitude(str string) bool
func IsLongitude(str string) bool
func IsLowerCase(str string) bool
func IsMAC(str string) bool
func IsMongoID(str string) bool
func IsMultibyte(str string) bool
func IsNatural(value float64) bool
func IsNegative(value float64) bool
func IsNonNegative(value float64) bool
func IsNonPositive(value float64) bool
func IsNull(str string) bool
func IsNumeric(str string) bool
func IsPositive(value float64) bool
func IsPrintableASCII(str string) bool
func IsRGBcolor(str string) bool
func IsRequestURI(rawurl string) bool
func IsRequestURL(rawurl string) bool
func IsSSN(str string) bool
func IsURL(str string) bool
func IsUTFDigit(str string) bool
func IsUTFLetter(str string) bool
func IsUTFLetterNumeric(str string) bool
func IsUTFNumeric(str string) bool
func IsUUID(str string) bool
func IsUUIDv3(str string) bool
func IsUUIDv4(str string) bool
func IsUUIDv5(str string) bool
func IsUpperCase(str string) bool
func IsVariableWidth(str string) bool
func IsWhole(value float64) bool
func LeftTrim(str, chars string) string
func Map(array []interface{}, iterator ResultIterator) []interface{}
func Matches(str, pattern string) bool
func NormalizeEmail(str string) (string, error)
func RemoveTags(s string) string
func ReplacePattern(str, pattern, replace string) string
func Reverse(s string) string
func RightTrim(str, chars string) string
func SafeFileName(str string) string
func Sign(value float64) float64
func StripLow(str string, keepNewLines bool) string
func ToBoolean(str string) (bool, error)
func ToFloat(str string) (float64, error)
func ToInt(str string) (int64, error)
func ToJSON(obj interface{}) (string, error)
func ToString(obj interface{}) string
func Trim(str, chars string) string
func Truncate(str string, length int, ending string) string
func UnderscoreToCamelCase(s string) string
func ValidateStruct(s interface{}) (bool, error)
func WhiteList(str, chars string) string
type ConditionIterator
type Error
func (e Error) Error() string
type Errors
func (es Errors) Error() string
type ISO3166Entry
type Iterator
type ParamValidator
type ResultIterator
type UnsupportedTypeError
func (e *UnsupportedTypeError) Error() string
type Validator
```
#### Examples
###### IsURL
```go
println(govalidator.IsURL(`http://user@pass:domain.com/path/page`))
```
###### ToString
```go
type User struct {
FirstName string
LastName string
}
str, _ := govalidator.ToString(&User{"John", "Juan"})
println(str)
```
###### Each, Map, Filter, Count for slices
Each iterates over the slice/array and calls Iterator for every item
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.Iterator = func(value interface{}, index int) {
println(value.(int))
}
govalidator.Each(data, fn)
```
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.ResultIterator = func(value interface{}, index int) interface{} {
return value.(int) * 3
}
_ = govalidator.Map(data, fn) // result = []interface{}{1, 6, 9, 12, 15}
```
```go
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var fn govalidator.ConditionIterator = func(value interface{}, index int) bool {
return value.(int)%2 == 0
}
_ = govalidator.Filter(data, fn) // result = []interface{}{2, 4, 6, 8, 10}
_ = govalidator.Count(data, fn) // result = 5
```
###### ValidateStruct [#2](https://github.com/asaskevich/govalidator/pull/2)
If you want to validate structs, you can use tag `valid` for any field in your structure. All validators used with this field in one tag are separated by comma. If you want to skip validation, place `-` in your tag. If you need a validator that is not on the list below, you can add it like this:
```go
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
```
Here is a list of available validators for struct fields (validator - used function):
```go
"alpha": IsAlpha,
"alphanum": IsAlphanumeric,
"ascii": IsASCII,
"base64": IsBase64,
"creditcard": IsCreditCard,
"datauri": IsDataURI,
"email": IsEmail,
"float": IsFloat,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"int": IsInt,
"ip": IsIP,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"latitude": IsLatitude,
"longitude": IsLongitude,
"lowercase": IsLowerCase,
"mac": IsMAC,
"multibyte": IsMultibyte,
"null": IsNull,
"numeric": IsNumeric,
"printableascii": IsPrintableASCII,
"requri": IsRequestURI,
"requrl": IsRequestURL,
"rgbcolor": IsRGBcolor,
"ssn": IsSSN
"uppercase": IsUpperCase,
"url": IsURL,
"utfdigit": IsUTFDigit,
"utfletter": IsUTFLetter,
"utfletternum": IsUTFLetterNumeric,
"utfnumeric": IsUTFNumeric,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"variablewidth": IsVariableWidth,
```
Validators with parameters
```go
"length(min|max)": ByteLength,
```
And here is small example of usage:
```go
type Post struct {
Title string `valid:"alphanum,required"`
Message string `valid:"duck,ascii"`
AuthorIP string `valid:"ipv4"`
Date string `valid:"-"`
}
post := &Post{
Title: "My Example Post",
Message: "duck",
AuthorIP: "123.234.54.3",
}
// Add your own struct validation tags
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
result, err := govalidator.ValidateStruct(post)
if err != nil {
println("error: " + err.Error())
}
println(result)
```
###### WhiteList
```go
// Remove all characters from string ignoring characters between "a" and "z"
println(govalidator.WhiteList("a3a43a5a4a3a2a23a4a5a4a3a4", "a-z") == "aaaaaaaaaaaa")
```
#### Notes
Documentation is available here: [godoc.org](https://godoc.org/github.com/asaskevich/govalidator).
Full information about code coverage is also available here: [govalidator on gocover.io](http://gocover.io/github.com/asaskevich/govalidator).
#### Support
If you do have a contribution for the package feel free to put up a Pull Request or open Issue.
#### Special thanks to [contributors](https://github.com/asaskevich/govalidator/graphs/contributors)
* [Attila Oláh](https://github.com/attilaolah)
* [Daniel Korner](https://github.com/Dadie)
* [Steven Wilkin](https://github.com/stevenwilkin)
* [Deiwin Sarjas](https://github.com/deiwin)
* [Noah Shibley](https://github.com/slugmobile)
* [Nathan Davies](https://github.com/nathj07)
* [Matt Sanford](https://github.com/mzsanford)
* [Simon ccl1115](https://github.com/ccl1115)
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/asaskevich/govalidator/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

View File

@ -0,0 +1,58 @@
package govalidator
// Iterator is the function that accepts element of slice/array and its index
type Iterator func(interface{}, int)
// ResultIterator is the function that accepts element of slice/array and its index and returns any result
type ResultIterator func(interface{}, int) interface{}
// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean
type ConditionIterator func(interface{}, int) bool
// Each iterates over the slice and apply Iterator to every item
func Each(array []interface{}, iterator Iterator) {
for index, data := range array {
iterator(data, index)
}
}
// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result.
func Map(array []interface{}, iterator ResultIterator) []interface{} {
var result []interface{} = make([]interface{}, len(array))
for index, data := range array {
result[index] = iterator(data, index)
}
return result
}
// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise.
func Find(array []interface{}, iterator ConditionIterator) interface{} {
for index, data := range array {
if iterator(data, index) {
return data
}
}
return nil
}
// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice.
func Filter(array []interface{}, iterator ConditionIterator) []interface{} {
var result []interface{} = make([]interface{}, 0)
for index, data := range array {
if iterator(data, index) {
result = append(result, data)
}
}
return result
}
// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator.
func Count(array []interface{}, iterator ConditionIterator) int {
count := 0
for index, data := range array {
if iterator(data, index) {
count = count + 1
}
}
return count
}

View File

@ -0,0 +1,49 @@
package govalidator
import (
"encoding/json"
"fmt"
"strconv"
)
// ToString convert the input to a string.
func ToString(obj interface{}) string {
res := fmt.Sprintf("%v", obj)
return string(res)
}
// ToJSON convert the input to a valid JSON string
func ToJSON(obj interface{}) (string, error) {
res, err := json.Marshal(obj)
if err != nil {
res = []byte("")
}
return string(res), err
}
// ToFloat convert the input string to a float, or 0.0 if the input is not a float.
func ToFloat(str string) (float64, error) {
res, err := strconv.ParseFloat(str, 64)
if err != nil {
res = 0.0
}
return res, err
}
// ToInt convert the input string to an integer, or 0 if the input is not an integer.
func ToInt(str string) (int64, error) {
res, err := strconv.ParseInt(str, 0, 64)
if err != nil {
res = 0
}
return res, err
}
// ToBoolean convert the input string to a boolean.
func ToBoolean(str string) (bool, error) {
res, err := strconv.ParseBool(str)
if err != nil {
res = false
}
return res, err
}

View File

@ -0,0 +1,24 @@
package govalidator
type Errors []error
func (es Errors) Errors() []error {
return es
}
func (es Errors) Error() string {
var err string
for _, e := range es {
err += e.Error() + ";"
}
return err
}
type Error struct {
Name string
Err error
}
func (e Error) Error() string {
return e.Name + ": " + e.Err.Error()
}

View File

@ -0,0 +1,57 @@
package govalidator
import "math"
// Abs returns absolute value of number
func Abs(value float64) float64 {
return value * Sign(value)
}
// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise
func Sign(value float64) float64 {
if value > 0 {
return 1
} else if value < 0 {
return -1
} else {
return 0
}
}
// IsNegative returns true if value < 0
func IsNegative(value float64) bool {
return value < 0
}
// IsPositive returns true if value > 0
func IsPositive(value float64) bool {
return value > 0
}
// IsNonNegative returns true if value >= 0
func IsNonNegative(value float64) bool {
return value >= 0
}
// IsNonPositive returns true if value <= 0
func IsNonPositive(value float64) bool {
return value <= 0
}
// InRange returns true if value lies between left and right border
func InRange(value, left, right float64) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// IsWhole returns true if value is whole number
func IsWhole(value float64) bool {
return Abs(math.Remainder(value, 1)) == 0
}
// IsNatural returns true if value is natural number (positive and whole)
func IsNatural(value float64) bool {
return IsWhole(value) && IsPositive(value)
}

View File

@ -0,0 +1,79 @@
package govalidator
import "regexp"
// Basic regular expressions for validating strings
const (
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
ISBN13 string = "^(?:[0-9]{13})$"
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[-+]?[0-9]+$"
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
Hexadecimal string = "^[0-9a-fA-F]+$"
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
ASCII string = "^[\x00-\x7F]+$"
Multibyte string = "[^\x00-\x7F]"
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
PrintableASCII string = "^[\x20-\x7E]+$"
DataURI string = "^data:.+\\/(.+);base64$"
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
URL string = `^((ftp|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^((?:\/[a-zA-Z0-9\.\:]+(?:_[a-zA-Z0-9\:\.]+)*(?:\-[\:a-zA-Z0-9\.]+)*)+\/?)$`
tagName string = "valid"
)
// Used by IsFilePath func
const (
// Unknown is unresolved OS type
Unknown = iota
// Win is Windows type
Win
// Unix is *nix OS types
Unix
)
var (
rxEmail = regexp.MustCompile(Email)
rxCreditCard = regexp.MustCompile(CreditCard)
rxISBN10 = regexp.MustCompile(ISBN10)
rxISBN13 = regexp.MustCompile(ISBN13)
rxUUID3 = regexp.MustCompile(UUID3)
rxUUID4 = regexp.MustCompile(UUID4)
rxUUID5 = regexp.MustCompile(UUID5)
rxUUID = regexp.MustCompile(UUID)
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
rxInt = regexp.MustCompile(Int)
rxFloat = regexp.MustCompile(Float)
rxHexadecimal = regexp.MustCompile(Hexadecimal)
rxHexcolor = regexp.MustCompile(Hexcolor)
rxRGBcolor = regexp.MustCompile(RGBcolor)
rxASCII = regexp.MustCompile(ASCII)
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
rxMultibyte = regexp.MustCompile(Multibyte)
rxFullWidth = regexp.MustCompile(FullWidth)
rxHalfWidth = regexp.MustCompile(HalfWidth)
rxBase64 = regexp.MustCompile(Base64)
rxDataURI = regexp.MustCompile(DataURI)
rxLatitude = regexp.MustCompile(Latitude)
rxLongitude = regexp.MustCompile(Longitude)
rxURL = regexp.MustCompile(URL)
rxSSN = regexp.MustCompile(SSN)
rxWinPath = regexp.MustCompile(WinPath)
rxUnixPath = regexp.MustCompile(UnixPath)
)

View File

@ -0,0 +1,349 @@
package govalidator
import (
"reflect"
"regexp"
)
// Validator is a wrapper for a validator function that returns bool and accepts string.
type Validator func(str string) bool
// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type.
type CustomTypeValidator func(i interface{}) bool
// ParamValidator is a wrapper for validator functions that accepts additional parameters.
type ParamValidator func(str string, params ...string) bool
type tagOptions []string
// UnsupportedTypeError is a wrapper for reflect.Type
type UnsupportedTypeError struct {
Type reflect.Type
}
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements the methods to sort by string.
type stringValues []reflect.Value
// ParamTagMap is a map of functions accept variants parameters
var ParamTagMap = map[string]ParamValidator{
"length": ByteLength,
"stringlength": StringLength,
}
var ParamTagRegexMap = map[string]*regexp.Regexp{
"length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"),
"stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"),
}
// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function.
// Use this to validate compound or custom types that need to be handled as a whole, e.g.
// `type UUID [16]byte` (this would be handled as an array of bytes).
var CustomTypeTagMap = map[string]CustomTypeValidator{}
// TagMap is a map of functions, that can be used as tags for ValidateStruct function.
var TagMap = map[string]Validator{
"email": IsEmail,
"url": IsURL,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
}
// ISO3166Entry stores country codes
type ISO3166Entry struct {
EnglishShortName string
FrenchShortName string
Alpha2Code string
Alpha3Code string
Numeric string
}
//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes"
var ISO3166List = []ISO3166Entry{
{"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"},
{"Albania", "Albanie (l')", "AL", "ALB", "008"},
{"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"},
{"Algeria", "Algérie (l')", "DZ", "DZA", "012"},
{"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"},
{"Andorra", "Andorre (l')", "AD", "AND", "020"},
{"Angola", "Angola (l')", "AO", "AGO", "024"},
{"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"},
{"Azerbaijan", "Azerbaïdjan (l')", "AZ", "AZE", "031"},
{"Argentina", "Argentine (l')", "AR", "ARG", "032"},
{"Australia", "Australie (l')", "AU", "AUS", "036"},
{"Austria", "Autriche (l')", "AT", "AUT", "040"},
{"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"},
{"Bahrain", "Bahreïn", "BH", "BHR", "048"},
{"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"},
{"Armenia", "Arménie (l')", "AM", "ARM", "051"},
{"Barbados", "Barbade (la)", "BB", "BRB", "052"},
{"Belgium", "Belgique (la)", "BE", "BEL", "056"},
{"Bermuda", "Bermudes (les)", "BM", "BMU", "060"},
{"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"},
{"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"},
{"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"},
{"Botswana", "Botswana (le)", "BW", "BWA", "072"},
{"Bouvet Island", "Bouvet (l'Île)", "BV", "BVT", "074"},
{"Brazil", "Brésil (le)", "BR", "BRA", "076"},
{"Belize", "Belize (le)", "BZ", "BLZ", "084"},
{"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"},
{"Solomon Islands", "Salomon (Îles)", "SB", "SLB", "090"},
{"Virgin Islands (British)", "Vierges britanniques (les Îles)", "VG", "VGB", "092"},
{"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"},
{"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"},
{"Myanmar", "Myanmar (le)", "MM", "MMR", "104"},
{"Burundi", "Burundi (le)", "BI", "BDI", "108"},
{"Belarus", "Bélarus (le)", "BY", "BLR", "112"},
{"Cambodia", "Cambodge (le)", "KH", "KHM", "116"},
{"Cameroon", "Cameroun (le)", "CM", "CMR", "120"},
{"Canada", "Canada (le)", "CA", "CAN", "124"},
{"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"},
{"Cayman Islands (the)", "Caïmans (les Îles)", "KY", "CYM", "136"},
{"Central African Republic (the)", "République centrafricaine (la)", "CF", "CAF", "140"},
{"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"},
{"Chad", "Tchad (le)", "TD", "TCD", "148"},
{"Chile", "Chili (le)", "CL", "CHL", "152"},
{"China", "Chine (la)", "CN", "CHN", "156"},
{"Taiwan (Province of China)", "Taïwan (Province de Chine)", "TW", "TWN", "158"},
{"Christmas Island", "Christmas (l'Île)", "CX", "CXR", "162"},
{"Cocos (Keeling) Islands (the)", "Cocos (les Îles)/ Keeling (les Îles)", "CC", "CCK", "166"},
{"Colombia", "Colombie (la)", "CO", "COL", "170"},
{"Comoros (the)", "Comores (les)", "KM", "COM", "174"},
{"Mayotte", "Mayotte", "YT", "MYT", "175"},
{"Congo (the)", "Congo (le)", "CG", "COG", "178"},
{"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"},
{"Cook Islands (the)", "Cook (les Îles)", "CK", "COK", "184"},
{"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"},
{"Croatia", "Croatie (la)", "HR", "HRV", "191"},
{"Cuba", "Cuba", "CU", "CUB", "192"},
{"Cyprus", "Chypre", "CY", "CYP", "196"},
{"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"},
{"Benin", "Bénin (le)", "BJ", "BEN", "204"},
{"Denmark", "Danemark (le)", "DK", "DNK", "208"},
{"Dominica", "Dominique (la)", "DM", "DMA", "212"},
{"Dominican Republic (the)", "dominicaine (la République)", "DO", "DOM", "214"},
{"Ecuador", "Équateur (l')", "EC", "ECU", "218"},
{"El Salvador", "El Salvador", "SV", "SLV", "222"},
{"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"},
{"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"},
{"Eritrea", "Érythrée (l')", "ER", "ERI", "232"},
{"Estonia", "Estonie (l')", "EE", "EST", "233"},
{"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"},
{"Falkland Islands (the) [Malvinas]", "Falkland (les Îles)/Malouines (les Îles)", "FK", "FLK", "238"},
{"South Georgia and the South Sandwich Islands", "Géorgie du Sud-et-les Îles Sandwich du Sud (la)", "GS", "SGS", "239"},
{"Fiji", "Fidji (les)", "FJ", "FJI", "242"},
{"Finland", "Finlande (la)", "FI", "FIN", "246"},
{"Åland Islands", "Åland(les Îles)", "AX", "ALA", "248"},
{"France", "France (la)", "FR", "FRA", "250"},
{"French Guiana", "Guyane française (la )", "GF", "GUF", "254"},
{"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"},
{"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"},
{"Djibouti", "Djibouti", "DJ", "DJI", "262"},
{"Gabon", "Gabon (le)", "GA", "GAB", "266"},
{"Georgia", "Géorgie (la)", "GE", "GEO", "268"},
{"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"},
{"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"},
{"Germany", "Allemagne (l')", "DE", "DEU", "276"},
{"Ghana", "Ghana (le)", "GH", "GHA", "288"},
{"Gibraltar", "Gibraltar", "GI", "GIB", "292"},
{"Kiribati", "Kiribati", "KI", "KIR", "296"},
{"Greece", "Grèce (la)", "GR", "GRC", "300"},
{"Greenland", "Groenland (le)", "GL", "GRL", "304"},
{"Grenada", "Grenade (la)", "GD", "GRD", "308"},
{"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"},
{"Guam", "Guam", "GU", "GUM", "316"},
{"Guatemala", "Guatemala (le)", "GT", "GTM", "320"},
{"Guinea", "Guinée (la)", "GN", "GIN", "324"},
{"Guyana", "Guyana (le)", "GY", "GUY", "328"},
{"Haiti", "Haïti", "HT", "HTI", "332"},
{"Heard Island and McDonald Islands", "Heard-et-Îles MacDonald (l'Île)", "HM", "HMD", "334"},
{"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"},
{"Honduras", "Honduras (le)", "HN", "HND", "340"},
{"Hong Kong", "Hong Kong", "HK", "HKG", "344"},
{"Hungary", "Hongrie (la)", "HU", "HUN", "348"},
{"Iceland", "Islande (l')", "IS", "ISL", "352"},
{"India", "Inde (l')", "IN", "IND", "356"},
{"Indonesia", "Indonésie (l')", "ID", "IDN", "360"},
{"Iran (Islamic Republic of)", "Iran (République Islamique d')", "IR", "IRN", "364"},
{"Iraq", "Iraq (l')", "IQ", "IRQ", "368"},
{"Ireland", "Irlande (l')", "IE", "IRL", "372"},
{"Israel", "Israël", "IL", "ISR", "376"},
{"Italy", "Italie (l')", "IT", "ITA", "380"},
{"Côte d'Ivoire", "Côte d'Ivoire (la)", "CI", "CIV", "384"},
{"Jamaica", "Jamaïque (la)", "JM", "JAM", "388"},
{"Japan", "Japon (le)", "JP", "JPN", "392"},
{"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"},
{"Jordan", "Jordanie (la)", "JO", "JOR", "400"},
{"Kenya", "Kenya (le)", "KE", "KEN", "404"},
{"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"},
{"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"},
{"Kuwait", "Koweït (le)", "KW", "KWT", "414"},
{"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"},
{"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"},
{"Lebanon", "Liban (le)", "LB", "LBN", "422"},
{"Lesotho", "Lesotho (le)", "LS", "LSO", "426"},
{"Latvia", "Lettonie (la)", "LV", "LVA", "428"},
{"Liberia", "Libéria (le)", "LR", "LBR", "430"},
{"Libya", "Libye (la)", "LY", "LBY", "434"},
{"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"},
{"Lithuania", "Lituanie (la)", "LT", "LTU", "440"},
{"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"},
{"Macao", "Macao", "MO", "MAC", "446"},
{"Madagascar", "Madagascar", "MG", "MDG", "450"},
{"Malawi", "Malawi (le)", "MW", "MWI", "454"},
{"Malaysia", "Malaisie (la)", "MY", "MYS", "458"},
{"Maldives", "Maldives (les)", "MV", "MDV", "462"},
{"Mali", "Mali (le)", "ML", "MLI", "466"},
{"Malta", "Malte", "MT", "MLT", "470"},
{"Martinique", "Martinique (la)", "MQ", "MTQ", "474"},
{"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"},
{"Mauritius", "Maurice", "MU", "MUS", "480"},
{"Mexico", "Mexique (le)", "MX", "MEX", "484"},
{"Monaco", "Monaco", "MC", "MCO", "492"},
{"Mongolia", "Mongolie (la)", "MN", "MNG", "496"},
{"Moldova (the Republic of)", "Moldova , République de", "MD", "MDA", "498"},
{"Montenegro", "Monténégro (le)", "ME", "MNE", "499"},
{"Montserrat", "Montserrat", "MS", "MSR", "500"},
{"Morocco", "Maroc (le)", "MA", "MAR", "504"},
{"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"},
{"Oman", "Oman", "OM", "OMN", "512"},
{"Namibia", "Namibie (la)", "NA", "NAM", "516"},
{"Nauru", "Nauru", "NR", "NRU", "520"},
{"Nepal", "Népal (le)", "NP", "NPL", "524"},
{"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"},
{"Curaçao", "Curaçao", "CW", "CUW", "531"},
{"Aruba", "Aruba", "AW", "ABW", "533"},
{"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"},
{"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"},
{"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"},
{"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"},
{"New Zealand", "Nouvelle-Zélande (la)", "NZ", "NZL", "554"},
{"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"},
{"Niger (the)", "Niger (le)", "NE", "NER", "562"},
{"Nigeria", "Nigéria (le)", "NG", "NGA", "566"},
{"Niue", "Niue", "NU", "NIU", "570"},
{"Norfolk Island", "Norfolk (l'Île)", "NF", "NFK", "574"},
{"Norway", "Norvège (la)", "NO", "NOR", "578"},
{"Northern Mariana Islands (the)", "Mariannes du Nord (les Îles)", "MP", "MNP", "580"},
{"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"},
{"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"},
{"Marshall Islands (the)", "Marshall (Îles)", "MH", "MHL", "584"},
{"Palau", "Palaos (les)", "PW", "PLW", "585"},
{"Pakistan", "Pakistan (le)", "PK", "PAK", "586"},
{"Panama", "Panama (le)", "PA", "PAN", "591"},
{"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"},
{"Paraguay", "Paraguay (le)", "PY", "PRY", "600"},
{"Peru", "Pérou (le)", "PE", "PER", "604"},
{"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"},
{"Pitcairn", "Pitcairn", "PN", "PCN", "612"},
{"Poland", "Pologne (la)", "PL", "POL", "616"},
{"Portugal", "Portugal (le)", "PT", "PRT", "620"},
{"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"},
{"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"},
{"Puerto Rico", "Porto Rico", "PR", "PRI", "630"},
{"Qatar", "Qatar (le)", "QA", "QAT", "634"},
{"Réunion", "Réunion (La)", "RE", "REU", "638"},
{"Romania", "Roumanie (la)", "RO", "ROU", "642"},
{"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"},
{"Rwanda", "Rwanda (le)", "RW", "RWA", "646"},
{"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"},
{"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"},
{"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"},
{"Anguilla", "Anguilla", "AI", "AIA", "660"},
{"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"},
{"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"},
{"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"},
{"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"},
{"San Marino", "Saint-Marin", "SM", "SMR", "674"},
{"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"},
{"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"},
{"Senegal", "Sénégal (le)", "SN", "SEN", "686"},
{"Serbia", "Serbie (la)", "RS", "SRB", "688"},
{"Seychelles", "Seychelles (les)", "SC", "SYC", "690"},
{"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"},
{"Singapore", "Singapour", "SG", "SGP", "702"},
{"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"},
{"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"},
{"Slovenia", "Slovénie (la)", "SI", "SVN", "705"},
{"Somalia", "Somalie (la)", "SO", "SOM", "706"},
{"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"},
{"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"},
{"Spain", "Espagne (l')", "ES", "ESP", "724"},
{"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"},
{"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"},
{"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"},
{"Suriname", "Suriname (le)", "SR", "SUR", "740"},
{"Svalbard and Jan Mayen", "Svalbard et l'Île Jan Mayen (le)", "SJ", "SJM", "744"},
{"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"},
{"Sweden", "Suède (la)", "SE", "SWE", "752"},
{"Switzerland", "Suisse (la)", "CH", "CHE", "756"},
{"Syrian Arab Republic", "République arabe syrienne (la)", "SY", "SYR", "760"},
{"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"},
{"Thailand", "Thaïlande (la)", "TH", "THA", "764"},
{"Togo", "Togo (le)", "TG", "TGO", "768"},
{"Tokelau", "Tokelau (les)", "TK", "TKL", "772"},
{"Tonga", "Tonga (les)", "TO", "TON", "776"},
{"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"},
{"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"},
{"Tunisia", "Tunisie (la)", "TN", "TUN", "788"},
{"Turkey", "Turquie (la)", "TR", "TUR", "792"},
{"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"},
{"Turks and Caicos Islands (the)", "Turks-et-Caïcos (les Îles)", "TC", "TCA", "796"},
{"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"},
{"Uganda", "Ouganda (l')", "UG", "UGA", "800"},
{"Ukraine", "Ukraine (l')", "UA", "UKR", "804"},
{"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'exRépublique yougoslave de)", "MK", "MKD", "807"},
{"Egypt", "Égypte (l')", "EG", "EGY", "818"},
{"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"},
{"Guernsey", "Guernesey", "GG", "GGY", "831"},
{"Jersey", "Jersey", "JE", "JEY", "832"},
{"Isle of Man", "Île de Man", "IM", "IMN", "833"},
{"Tanzania, United Republic of", "Tanzanie, République-Unie de", "TZ", "TZA", "834"},
{"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"},
{"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"},
{"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"},
{"Uruguay", "Uruguay (l')", "UY", "URY", "858"},
{"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"},
{"Venezuela (Bolivarian Republic of)", "Venezuela (République bolivarienne du)", "VE", "VEN", "862"},
{"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"},
{"Samoa", "Samoa (le)", "WS", "WSM", "882"},
{"Yemen", "Yémen (le)", "YE", "YEM", "887"},
{"Zambia", "Zambie (la)", "ZM", "ZMB", "894"},
}

View File

@ -0,0 +1,214 @@
package govalidator
import (
"errors"
"fmt"
"html"
"path"
"regexp"
"strings"
"unicode"
)
// Contains check if the string contains the substring.
func Contains(str, substring string) bool {
return strings.Contains(str, substring)
}
// Matches check if string matches the pattern (pattern is regular expression)
// In case of error return false
func Matches(str, pattern string) bool {
match, _ := regexp.MatchString(pattern, str)
return match
}
// LeftTrim trim characters from the left-side of the input.
// If second argument is empty, it's will be remove leading spaces.
func LeftTrim(str, chars string) string {
pattern := ""
if chars == "" {
pattern = "^\\s+"
} else {
pattern = "^[" + chars + "]+"
}
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// RightTrim trim characters from the right-side of the input.
// If second argument is empty, it's will be remove spaces.
func RightTrim(str, chars string) string {
pattern := ""
if chars == "" {
pattern = "\\s+$"
} else {
pattern = "[" + chars + "]+$"
}
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// Trim trim characters from both sides of the input.
// If second argument is empty, it's will be remove spaces.
func Trim(str, chars string) string {
return LeftTrim(RightTrim(str, chars), chars)
}
// WhiteList remove characters that do not appear in the whitelist.
func WhiteList(str, chars string) string {
pattern := "[^" + chars + "]+"
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// BlackList remove characters that appear in the blacklist.
func BlackList(str, chars string) string {
pattern := "[" + chars + "]+"
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// StripLow remove characters with a numerical value < 32 and 127, mostly control characters.
// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
func StripLow(str string, keepNewLines bool) string {
chars := ""
if keepNewLines {
chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F"
} else {
chars = "\x00-\x1F\x7F"
}
return BlackList(str, chars)
}
// ReplacePattern replace regular expression pattern in string
func ReplacePattern(str, pattern, replace string) string {
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte(replace)))
}
// Escape replace <, >, & and " with HTML entities.
var Escape = html.EscapeString
func addSegment(inrune, segment []rune) []rune {
if len(segment) == 0 {
return inrune
}
if len(inrune) != 0 {
inrune = append(inrune, '_')
}
inrune = append(inrune, segment...)
return inrune
}
// UnderscoreToCamelCase converts from underscore separated form to camel case form.
// Ex.: my_func => MyFunc
func UnderscoreToCamelCase(s string) string {
return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1)
}
// CamelCaseToUnderscore converts from camel case form to underscore separated form.
// Ex.: MyFunc => my_func
func CamelCaseToUnderscore(str string) string {
var output []rune
var segment []rune
for _, r := range str {
if !unicode.IsLower(r) {
output = addSegment(output, segment)
segment = nil
}
segment = append(segment, unicode.ToLower(r))
}
output = addSegment(output, segment)
return string(output)
}
// Reverse return reversed string
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
// GetLines split string by "\n" and return array of lines
func GetLines(s string) []string {
return strings.Split(s, "\n")
}
// GetLine return specified line of multiline string
func GetLine(s string, index int) (string, error) {
lines := GetLines(s)
if index < 0 || index >= len(lines) {
return "", errors.New("line index out of bounds")
}
return lines[index], nil
}
// RemoveTags remove all tags from HTML string
func RemoveTags(s string) string {
return ReplacePattern(s, "<[^>]*>", "")
}
// SafeFileName return safe string that can be used in file names
func SafeFileName(str string) string {
name := strings.ToLower(str)
name = path.Clean(path.Base(name))
name = strings.Trim(name, " ")
separators, err := regexp.Compile(`[ &_=+:]`)
if err == nil {
name = separators.ReplaceAllString(name, "-")
}
legal, err := regexp.Compile(`[^[:alnum:]-.]`)
if err == nil {
name = legal.ReplaceAllString(name, "")
}
for strings.Contains(name, "--") {
name = strings.Replace(name, "--", "-", -1)
}
return name
}
// NormalizeEmail canonicalize an email address.
// The local part of the email address is lowercased for all domains; the hostname is always lowercased and
// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail).
// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and
// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are
// normalized to @gmail.com.
func NormalizeEmail(str string) (string, error) {
if !IsEmail(str) {
return "", fmt.Errorf("%s is not an email", str)
}
parts := strings.Split(str, "@")
parts[0] = strings.ToLower(parts[0])
parts[1] = strings.ToLower(parts[1])
if parts[1] == "gmail.com" || parts[1] == "googlemail.com" {
parts[1] = "gmail.com"
parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0]
}
return strings.Join(parts, "@"), nil
}
// Will truncate a string closest length without breaking words.
func Truncate(str string, length int, ending string) string {
var aftstr, befstr string
if len(str) > length {
words := strings.Fields(str)
before, present := 0, 0
for i := range words {
befstr = aftstr
before = present
aftstr = aftstr + words[i] + " "
present = len(aftstr)
if present > length && i != 0 {
if (length - before) < (present - length) {
return Trim(befstr, " /\\.,\"'#!?&@+-") + ending
} else {
return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending
}
}
}
}
return str
}

View File

@ -0,0 +1,854 @@
// Package govalidator is package of validators and sanitizers for strings, structs and collections.
package govalidator
import (
"encoding/json"
"fmt"
"net"
"net/url"
"reflect"
"regexp"
"sort"
"strings"
"unicode"
"unicode/utf8"
)
var fieldsRequiredByDefault bool
// SetFieldsRequiredByDefault causes validation to fail when struct fields
// do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`).
// This struct definition will fail govalidator.ValidateStruct() (and the field values do not matter):
// type exampleStruct struct {
// Name string ``
// Email string `valid:"email"`
// This, however, will only fail when Email is empty or an invalid email address:
// type exampleStruct2 struct {
// Name string `valid:"-"`
// Email string `valid:"email"`
// Lastly, this will only fail when Email is an invalid email address but not when it's empty:
// type exampleStruct2 struct {
// Name string `valid:"-"`
// Email string `valid:"email,optional"`
func SetFieldsRequiredByDefault(value bool) {
fieldsRequiredByDefault = value
}
// IsEmail check if the string is an email.
func IsEmail(str string) bool {
// TODO uppercase letters are not supported
return rxEmail.MatchString(str)
}
// IsURL check if the string is an URL.
func IsURL(str string) bool {
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
return false
}
u, err := url.Parse(str)
if err != nil {
return false
}
if strings.HasPrefix(u.Host, ".") {
return false
}
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
return false
}
return rxURL.MatchString(str)
}
// IsRequestURL check if the string rawurl, assuming
// it was recieved in an HTTP request, is a valid
// URL confirm to RFC 3986
func IsRequestURL(rawurl string) bool {
url, err := url.ParseRequestURI(rawurl)
if err != nil {
return false //Couldn't even parse the rawurl
}
if len(url.Scheme) == 0 {
return false //No Scheme found
}
return true
}
// IsRequestURI check if the string rawurl, assuming
// it was recieved in an HTTP request, is an
// absolute URI or an absolute path.
func IsRequestURI(rawurl string) bool {
_, err := url.ParseRequestURI(rawurl)
return err == nil
}
// IsAlpha check if the string contains only letters (a-zA-Z). Empty string is valid.
func IsAlpha(str string) bool {
if IsNull(str) {
return true
}
return rxAlpha.MatchString(str)
}
//IsUTFLetter check if the string contains only unicode letter characters.
//Similar to IsAlpha but for all languages. Empty string is valid.
func IsUTFLetter(str string) bool {
if IsNull(str) {
return true
}
for _, c := range str {
if !unicode.IsLetter(c) {
return false
}
}
return true
}
// IsAlphanumeric check if the string contains only letters and numbers. Empty string is valid.
func IsAlphanumeric(str string) bool {
if IsNull(str) {
return true
}
return rxAlphanumeric.MatchString(str)
}
// IsUTFLetterNumeric check if the string contains only unicode letters and numbers. Empty string is valid.
func IsUTFLetterNumeric(str string) bool {
if IsNull(str) {
return true
}
for _, c := range str {
if !unicode.IsLetter(c) && !unicode.IsNumber(c) { //letters && numbers are ok
return false
}
}
return true
}
// IsNumeric check if the string contains only numbers. Empty string is valid.
func IsNumeric(str string) bool {
if IsNull(str) {
return true
}
return rxNumeric.MatchString(str)
}
// IsUTFNumeric check if the string contains only unicode numbers of any kind.
// Numbers can be 0-9 but also Fractions ¾,Roman Ⅸ and Hangzhou 〩. Empty string is valid.
func IsUTFNumeric(str string) bool {
if IsNull(str) {
return true
}
if strings.IndexAny(str, "+-") > 0 {
return false
}
if len(str) > 1 {
str = strings.TrimPrefix(str, "-")
str = strings.TrimPrefix(str, "+")
}
for _, c := range str {
if unicode.IsNumber(c) == false { //numbers && minus sign are ok
return false
}
}
return true
}
// IsUTFDigit check if the string contains only unicode radix-10 decimal digits. Empty string is valid.
func IsUTFDigit(str string) bool {
if IsNull(str) {
return true
}
if strings.IndexAny(str, "+-") > 0 {
return false
}
if len(str) > 1 {
str = strings.TrimPrefix(str, "-")
str = strings.TrimPrefix(str, "+")
}
for _, c := range str {
if !unicode.IsDigit(c) { //digits && minus sign are ok
return false
}
}
return true
}
// IsHexadecimal check if the string is a hexadecimal number.
func IsHexadecimal(str string) bool {
return rxHexadecimal.MatchString(str)
}
// IsHexcolor check if the string is a hexadecimal color.
func IsHexcolor(str string) bool {
return rxHexcolor.MatchString(str)
}
// IsRGBcolor check if the string is a valid RGB color in form rgb(RRR, GGG, BBB).
func IsRGBcolor(str string) bool {
return rxRGBcolor.MatchString(str)
}
// IsLowerCase check if the string is lowercase. Empty string is valid.
func IsLowerCase(str string) bool {
if IsNull(str) {
return true
}
return str == strings.ToLower(str)
}
// IsUpperCase check if the string is uppercase. Empty string is valid.
func IsUpperCase(str string) bool {
if IsNull(str) {
return true
}
return str == strings.ToUpper(str)
}
// IsInt check if the string is an integer. Empty string is valid.
func IsInt(str string) bool {
if IsNull(str) {
return true
}
return rxInt.MatchString(str)
}
// IsFloat check if the string is a float.
func IsFloat(str string) bool {
return str != "" && rxFloat.MatchString(str)
}
// IsDivisibleBy check if the string is a number that's divisible by another.
// If second argument is not valid integer or zero, it's return false.
// Otherwise, if first argument is not valid integer or zero, it's return true (Invalid string converts to zero).
func IsDivisibleBy(str, num string) bool {
f, _ := ToFloat(str)
p := int64(f)
q, _ := ToInt(num)
if q == 0 {
return false
}
return (p == 0) || (p%q == 0)
}
// IsNull check if the string is null.
func IsNull(str string) bool {
return len(str) == 0
}
// IsByteLength check if the string's length (in bytes) falls in a range.
func IsByteLength(str string, min, max int) bool {
return len(str) >= min && len(str) <= max
}
// IsUUIDv3 check if the string is a UUID version 3.
func IsUUIDv3(str string) bool {
return rxUUID3.MatchString(str)
}
// IsUUIDv4 check if the string is a UUID version 4.
func IsUUIDv4(str string) bool {
return rxUUID4.MatchString(str)
}
// IsUUIDv5 check if the string is a UUID version 5.
func IsUUIDv5(str string) bool {
return rxUUID5.MatchString(str)
}
// IsUUID check if the string is a UUID (version 3, 4 or 5).
func IsUUID(str string) bool {
return rxUUID.MatchString(str)
}
// IsCreditCard check if the string is a credit card.
func IsCreditCard(str string) bool {
r, _ := regexp.Compile("[^0-9]+")
sanitized := r.ReplaceAll([]byte(str), []byte(""))
if !rxCreditCard.MatchString(string(sanitized)) {
return false
}
var sum int64
var digit string
var tmpNum int64
var shouldDouble bool
for i := len(sanitized) - 1; i >= 0; i-- {
digit = string(sanitized[i:(i + 1)])
tmpNum, _ = ToInt(digit)
if shouldDouble {
tmpNum *= 2
if tmpNum >= 10 {
sum += ((tmpNum % 10) + 1)
} else {
sum += tmpNum
}
} else {
sum += tmpNum
}
shouldDouble = !shouldDouble
}
if sum%10 == 0 {
return true
}
return false
}
// IsISBN10 check if the string is an ISBN version 10.
func IsISBN10(str string) bool {
return IsISBN(str, 10)
}
// IsISBN13 check if the string is an ISBN version 13.
func IsISBN13(str string) bool {
return IsISBN(str, 13)
}
// IsISBN check if the string is an ISBN (version 10 or 13).
// If version value is not equal to 10 or 13, it will be check both variants.
func IsISBN(str string, version int) bool {
r, _ := regexp.Compile("[\\s-]+")
sanitized := r.ReplaceAll([]byte(str), []byte(""))
var checksum int32
var i int32
if version == 10 {
if !rxISBN10.MatchString(string(sanitized)) {
return false
}
for i = 0; i < 9; i++ {
checksum += (i + 1) * int32(sanitized[i]-'0')
}
if sanitized[9] == 'X' {
checksum += 10 * 10
} else {
checksum += 10 * int32(sanitized[9]-'0')
}
if checksum%11 == 0 {
return true
}
return false
} else if version == 13 {
if !rxISBN13.MatchString(string(sanitized)) {
return false
}
factor := []int32{1, 3}
for i = 0; i < 12; i++ {
checksum += factor[i%2] * int32(sanitized[i]-'0')
}
if (int32(sanitized[12]-'0'))-((10-(checksum%10))%10) == 0 {
return true
}
return false
}
return IsISBN(str, 10) || IsISBN(str, 13)
}
// IsJSON check if the string is valid JSON (note: uses json.Unmarshal).
func IsJSON(str string) bool {
var js json.RawMessage
return json.Unmarshal([]byte(str), &js) == nil
}
// IsMultibyte check if the string contains one or more multibyte chars. Empty string is valid.
func IsMultibyte(str string) bool {
if IsNull(str) {
return true
}
return rxMultibyte.MatchString(str)
}
// IsASCII check if the string contains ASCII chars only. Empty string is valid.
func IsASCII(str string) bool {
if IsNull(str) {
return true
}
return rxASCII.MatchString(str)
}
// IsPrintableASCII check if the string contains printable ASCII chars only. Empty string is valid.
func IsPrintableASCII(str string) bool {
if IsNull(str) {
return true
}
return rxPrintableASCII.MatchString(str)
}
// IsFullWidth check if the string contains any full-width chars. Empty string is valid.
func IsFullWidth(str string) bool {
if IsNull(str) {
return true
}
return rxFullWidth.MatchString(str)
}
// IsHalfWidth check if the string contains any half-width chars. Empty string is valid.
func IsHalfWidth(str string) bool {
if IsNull(str) {
return true
}
return rxHalfWidth.MatchString(str)
}
// IsVariableWidth check if the string contains a mixture of full and half-width chars. Empty string is valid.
func IsVariableWidth(str string) bool {
if IsNull(str) {
return true
}
return rxHalfWidth.MatchString(str) && rxFullWidth.MatchString(str)
}
// IsBase64 check if a string is base64 encoded.
func IsBase64(str string) bool {
return rxBase64.MatchString(str)
}
// IsFilePath check is a string is Win or Unix file path and returns it's type.
func IsFilePath(str string) (bool, int) {
if rxWinPath.MatchString(str) {
//check windows path limit see:
// http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath
if len(str[3:]) > 32767 {
return false, Win
}
return true, Win
} else if rxUnixPath.MatchString(str) {
return true, Unix
}
return false, Unknown
}
// IsDataURI checks if a string is base64 encoded data URI such as an image
func IsDataURI(str string) bool {
dataURI := strings.Split(str, ",")
if !rxDataURI.MatchString(dataURI[0]) {
return false
}
return IsBase64(dataURI[1])
}
// IsISO3166Alpha2 checks if a string is valid two-letter country code
func IsISO3166Alpha2(str string) bool {
for _, entry := range ISO3166List {
if str == entry.Alpha2Code {
return true
}
}
return false
}
// IsISO3166Alpha3 checks if a string is valid three-letter country code
func IsISO3166Alpha3(str string) bool {
for _, entry := range ISO3166List {
if str == entry.Alpha3Code {
return true
}
}
return false
}
// IsIP checks if a string is either IP version 4 or 6.
func IsIP(str string) bool {
return net.ParseIP(str) != nil
}
// IsIPv4 check if the string is an IP version 4.
func IsIPv4(str string) bool {
ip := net.ParseIP(str)
return ip != nil && ip.To4() != nil
}
// IsIPv6 check if the string is an IP version 6.
func IsIPv6(str string) bool {
ip := net.ParseIP(str)
return ip != nil && ip.To4() == nil
}
// IsMAC check if a string is valid MAC address.
// Possible MAC formats:
// 01:23:45:67:89:ab
// 01:23:45:67:89:ab:cd:ef
// 01-23-45-67-89-ab
// 01-23-45-67-89-ab-cd-ef
// 0123.4567.89ab
// 0123.4567.89ab.cdef
func IsMAC(str string) bool {
_, err := net.ParseMAC(str)
return err == nil
}
// IsMongoID check if the string is a valid hex-encoded representation of a MongoDB ObjectId.
func IsMongoID(str string) bool {
return rxHexadecimal.MatchString(str) && (len(str) == 24)
}
// IsLatitude check if a string is valid latitude.
func IsLatitude(str string) bool {
return rxLatitude.MatchString(str)
}
// IsLongitude check if a string is valid longitude.
func IsLongitude(str string) bool {
return rxLongitude.MatchString(str)
}
// ValidateStruct use tags for fields
func ValidateStruct(s interface{}) (bool, error) {
if s == nil {
return true, nil
}
result := true
var err error
val := reflect.ValueOf(s)
if val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr {
val = val.Elem()
}
// we only accept structs
if val.Kind() != reflect.Struct {
return false, fmt.Errorf("function only accepts structs; got %s", val.Kind())
}
var errs Errors
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
if typeField.PkgPath != "" {
continue // Private field
}
resultField, err := typeCheck(valueField, typeField)
if err != nil {
errs = append(errs, err)
}
result = result && resultField
}
if len(errs) > 0 {
err = errs
}
return result, err
}
// parseTag splits a struct field's tag into its
// comma-separated options.
func parseTag(tag string) tagOptions {
split := strings.SplitN(tag, ",", -1)
return tagOptions(split)
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
default:
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
return true
}
// IsSSN will validate the given string as a U.S. Social Security Number
func IsSSN(str string) bool {
if str == "" || len(str) != 11 {
return false
}
return rxSSN.MatchString(str)
}
// ByteLength check string's length
func ByteLength(str string, params ...string) bool {
if len(params) == 2 {
min, _ := ToInt(params[0])
max, _ := ToInt(params[1])
return len(str) >= int(min) && len(str) <= int(max)
}
return false
}
// StringLength check string's length (including multi byte strings)
func StringLength(str string, params ...string) bool {
if len(params) == 2 {
strLength := utf8.RuneCountInString(str)
min, _ := ToInt(params[0])
max, _ := ToInt(params[1])
return strLength >= int(min) && strLength <= int(max)
}
return false
}
// Contains returns whether checks that a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (opts tagOptions) contains(optionName string) bool {
for i := range opts {
tagOpt := opts[i]
if tagOpt == optionName {
return true
}
}
return false
}
func checkRequired(v reflect.Value, t reflect.StructField, options tagOptions) (bool, error) {
if options.contains("required") {
err := fmt.Errorf("non zero value required")
return false, Error{t.Name, err}
} else if fieldsRequiredByDefault && !options.contains("optional") {
err := fmt.Errorf("All fields are required to at least have one validation defined")
return false, Error{t.Name, err}
}
// not required and empty is valid
return true, nil
}
func typeCheck(v reflect.Value, t reflect.StructField) (bool, error) {
if !v.IsValid() {
return false, nil
}
tag := t.Tag.Get(tagName)
// Check if the field should be ignored
switch tag {
case "":
if !fieldsRequiredByDefault {
return true, nil
}
err := fmt.Errorf("All fields are required to at least have one validation defined")
return false, Error{t.Name, err}
case "-":
return true, nil
}
options := parseTag(tag)
for i := range options {
tagOpt := options[i]
if ok := isValidTag(tagOpt); !ok {
continue
}
if validatefunc, ok := CustomTypeTagMap[tagOpt]; ok {
options = append(options[:i], options[i+1:]...) // we found our custom validator, so remove it from the options
if result := validatefunc(v.Interface()); !result {
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", fmt.Sprint(v), tagOpt)}
}
return true, nil
}
}
if isEmptyValue(v) {
// an empty value is not validated, check only required
return checkRequired(v, t, options)
}
switch v.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64,
reflect.String:
// for each tag option check the map of validator functions
for i := range options {
tagOpt := options[i]
negate := false
// Check wether the tag looks like '!something' or 'something'
if len(tagOpt) > 0 && tagOpt[0] == '!' {
tagOpt = string(tagOpt[1:])
negate = true
}
if ok := isValidTag(tagOpt); !ok {
err := fmt.Errorf("Unkown Validator %s", tagOpt)
return false, Error{t.Name, err}
}
// Check for param validators
for key, value := range ParamTagRegexMap {
ps := value.FindStringSubmatch(tagOpt)
if len(ps) > 0 {
if validatefunc, ok := ParamTagMap[key]; ok {
switch v.Kind() {
case reflect.String:
field := fmt.Sprint(v) // make value into string, then validate with regex
if result := validatefunc(field, ps[1:]...); !result && !negate || result && negate {
var err error
if !negate {
err = fmt.Errorf("%s does not validate as %s", field, tagOpt)
} else {
err = fmt.Errorf("%s does validate as %s", field, tagOpt)
}
return false, Error{t.Name, err}
}
default:
//Not Yet Supported Types (Fail here!)
err := fmt.Errorf("Validator %s doesn't support kind %s", tagOpt, v.Kind())
return false, Error{t.Name, err}
}
}
}
}
if validatefunc, ok := TagMap[tagOpt]; ok {
switch v.Kind() {
case reflect.String:
field := fmt.Sprint(v) // make value into string, then validate with regex
if result := validatefunc(field); !result && !negate || result && negate {
var err error
if !negate {
err = fmt.Errorf("%s does not validate as %s", field, tagOpt)
} else {
err = fmt.Errorf("%s does validate as %s", field, tagOpt)
}
return false, Error{t.Name, err}
}
default:
//Not Yet Supported Types (Fail here!)
err := fmt.Errorf("Validator %s doesn't support kind %s for value %v", tagOpt, v.Kind(), v)
return false, Error{t.Name, err}
}
}
}
return true, nil
case reflect.Map:
if v.Type().Key().Kind() != reflect.String {
return false, &UnsupportedTypeError{v.Type()}
}
var sv stringValues
sv = v.MapKeys()
sort.Sort(sv)
result := true
for _, k := range sv {
resultItem, err := ValidateStruct(v.MapIndex(k).Interface())
if err != nil {
return false, err
}
result = result && resultItem
}
return result, nil
case reflect.Slice:
result := true
for i := 0; i < v.Len(); i++ {
var resultItem bool
var err error
if v.Index(i).Kind() != reflect.Struct {
resultItem, err = typeCheck(v.Index(i), t)
if err != nil {
return false, err
}
} else {
resultItem, err = ValidateStruct(v.Index(i).Interface())
if err != nil {
return false, err
}
}
result = result && resultItem
}
return result, nil
case reflect.Array:
result := true
for i := 0; i < v.Len(); i++ {
var resultItem bool
var err error
if v.Index(i).Kind() != reflect.Struct {
resultItem, err = typeCheck(v.Index(i), t)
if err != nil {
return false, err
}
} else {
resultItem, err = ValidateStruct(v.Index(i).Interface())
if err != nil {
return false, err
}
}
result = result && resultItem
}
return result, nil
case reflect.Interface:
// If the value is an interface then encode its element
if v.IsNil() {
return true, nil
}
return ValidateStruct(v.Interface())
case reflect.Ptr:
// If the value is a pointer then check its element
if v.IsNil() {
return true, nil
}
return typeCheck(v.Elem(), t)
case reflect.Struct:
return ValidateStruct(v.Interface())
default:
return false, &UnsupportedTypeError{v.Type()}
}
}
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.String, reflect.Array:
return v.Len() == 0
case reflect.Map, reflect.Slice:
return v.Len() == 0 || v.IsNil()
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}
// ErrorByField returns error for specified field of the struct
// validated by ValidateStruct or empty string if there are no errors
// or this field doesn't exists or doesn't have any errors.
func ErrorByField(e error, field string) string {
if e == nil {
return ""
}
return ErrorsByField(e)[field]
}
// ErrorsByField returns map of errors of the struct validated
// by ValidateStruct or empty map if there are no errors.
func ErrorsByField(e error) map[string]string {
m := make(map[string]string)
if e == nil {
return m
}
// prototype for ValidateStruct
switch e.(type) {
case Error:
m[e.(Error).Name] = e.(Error).Err.Error()
case Errors:
for _, item := range e.(Errors).Errors() {
m[item.(Error).Name] = item.(Error).Err.Error()
}
}
return m
}
// Error returns string equivalent for reflect.Type
func (e *UnsupportedTypeError) Error() string {
return "validator: unsupported type: " + e.Type.String()
}
func (sv stringValues) Len() int { return len(sv) }
func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) }
func (sv stringValues) get(i int) string { return sv[i].String() }

View File

@ -0,0 +1,15 @@
box: wercker/golang
build:
steps:
- setup-go-workspace
- script:
name: go get
code: |
go version
go get -t ./...
- script:
name: go test
code: |
go test -race ./...

View File

@ -4,7 +4,6 @@ import (
"fmt"
"time"
"github.com/fatih/structs"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
@ -64,7 +63,9 @@ func (b *backend) pathCRLRead(
}
return &logical.Response{
Data: structs.New(config).Map(),
Data: map[string]interface{}{
"expiry": config.Expiry,
},
}, nil
}

View File

@ -2,9 +2,9 @@ package pki
import (
"fmt"
"net/url"
"strings"
"github.com/asaskevich/govalidator"
"github.com/fatih/structs"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
@ -43,14 +43,14 @@ for the OCSP servers attribute`,
}
}
func validateURLs(urls []string) (string, error) {
func validateURLs(urls []string) string {
for _, curr := range urls {
if _, err := url.Parse(curr); err != nil {
return curr, err
if !govalidator.IsURL(curr) {
return curr
}
}
return "", nil
return ""
}
func getURLs(req *logical.Request) (*urlEntries, error) {
@ -121,25 +121,25 @@ func (b *backend) pathWriteURL(
if urlsInt, ok := data.GetOk("issuing_certificates"); ok {
splitURLs := strings.Split(urlsInt.(string), ",")
entries.IssuingCertificates = splitURLs
if badUrl, err := validateURLs(entries.CRLDistributionPoints); err != nil {
if badUrl := validateURLs(entries.IssuingCertificates); badUrl != "" {
return logical.ErrorResponse(fmt.Sprintf(
"invalid URL found in issuing certificates; url is %s, error is %s", badUrl, err)), nil
"invalid URL found in issuing certificates: %s", badUrl)), nil
}
}
if urlsInt, ok := data.GetOk("crl_distribution_points"); ok {
splitURLs := strings.Split(urlsInt.(string), ",")
entries.CRLDistributionPoints = splitURLs
if badUrl, err := validateURLs(entries.CRLDistributionPoints); err != nil {
if badUrl := validateURLs(entries.CRLDistributionPoints); badUrl != "" {
return logical.ErrorResponse(fmt.Sprintf(
"invalid URL found in CRL distribution points; url is %s, error is %s", badUrl, err)), nil
"invalid URL found in CRL distribution points: %s", badUrl)), nil
}
}
if urlsInt, ok := data.GetOk("ocsp_servers"); ok {
splitURLs := strings.Split(urlsInt.(string), ",")
entries.OCSPServers = splitURLs
if badUrl, err := validateURLs(entries.CRLDistributionPoints); err != nil {
if badUrl := validateURLs(entries.OCSPServers); badUrl != "" {
return logical.ErrorResponse(fmt.Sprintf(
"invalid URL found in OCSP servers; url is %s, error is %s", badUrl, err)), nil
"invalid URL found in OCSP servers: %s", badUrl)), nil
}
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"time"
"github.com/fatih/structs"
"github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
@ -164,14 +163,26 @@ func (b *backend) pathIssueSignCert(
}
resp := b.Secret(SecretCertsType).Response(
structs.New(cb).Map(),
map[string]interface{}{
"certificate": cb.Certificate,
"issuing_ca": cb.IssuingCA,
"serial_number": cb.SerialNumber,
},
map[string]interface{}{
"serial_number": cb.SerialNumber,
})
if !useCSR {
resp.Data["private_key"] = cb.PrivateKey
resp.Data["private_key_type"] = cb.PrivateKeyType
}
if format == "der" {
resp.Data["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes)
resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(parsedBundle.IssuingCABytes)
if !useCSR {
resp.Data["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes)
}
}
resp.Secret.TTL = parsedBundle.Certificate.NotAfter.Sub(time.Now())

View File

@ -336,7 +336,7 @@ type roleEntry struct {
UseCSRCommonName bool `json:"use_csr_common_name" structs:"use_csr_common_name" mapstructure:"use_csr_common_name"`
KeyType string `json:"key_type" structs:"key_type" mapstructure:"key_type"`
KeyBits int `json:"key_bits" structs:"key_bits" mapstructure:"key_bits"`
MaxPathLength *int `json:"," structs:"," mapstructure:","`
MaxPathLength *int `json:",omitempty" structs:",omitempty"`
}
const pathRoleHelpSyn = `

View File

@ -99,13 +99,12 @@ is down.
### You must configure issuing/CRL/OCSP information *in advance*
**N.B.**: This information is valid for 0.4+.
This backend serves CRLs from a predictable location, but it is not possible
for the backend to know where it is running. Therefore, you must configure
desired URLs for the issuing certificate, CRL distribution points, and OCSP
servers manually using the `config/urls` endpoint. It is supported to have more
than one of each of these.
than one of each of these by passing in the multiple URLs as a comma-separated
string parameter.
### No OCSP support, yet
@ -113,20 +112,16 @@ Vault's architecture does not currently allow for a binary protocol such as
OCSP to be supported by a backend. As such, you should configure your software
to use CRLs for revocation information, with a caching lifetime that feels good
to you. Since you are following the advice above about keeping lifetimes short
(right?), CRLs should not grow too large.
If OCSP is important to you, you can configure alternate CRL and/or OCSP
servers using `config/urls`.
(right?), CRLs should not grow too large, however, you can configure alternate
CRL and/or OCSP servers using `config/urls` if you wish.
If you are using issued certificates for client authentication to Vault, note
that as of 0.4, the `cert` authentication endpoint supports being pushed CRLs.
that as of 0.4, the `cert` authentication endpoint supports being pushed CRLs,
but it cannot read CRLs directly from this backend.
## Quick Start
**N.B.**: This quick start assumes that you are pushing a CA certificate and
private key in. Since 0.4 there are many different methods of getting CA
information into the backend according to your needs, so please note that this
is simply one example.
#### Mount the backend
The first step to using the PKI backend is to mount it. Unlike the `generic`
backend, the `pki` backend is not mounted by default.
@ -136,26 +131,98 @@ $ vault mount pki
Successfully mounted 'pki' at 'pki'!
```
Next, Vault must be configured with a CA certificate and associated private key. This is done by writing the contents of a file or *stdin*:
#### Configure a CA certificate
Next, Vault must be configured with a CA certificate and associated private
key. We'll take advantage of the backend's self-signed root generation support,
but Vault also supports generating an intermediate CA (with a CSR for signing)
or setting a PEM-encoded certificate and private key bundle directly into the
backend.
Generally you'll want a root certificate to only be used to sign CA
intermediate certificates, but for this example we'll proceed as if you will
issue certificates directly from the root. As it's a root, we'll want to set a
long maximum life time for the certificate; since it honors the maximum mount
TTL, first we adjust that:
```text
$ vault write pki/config/ca \
pem_bundle="@ca_bundle.pem"
Success! Data written to: pki/config/ca
$ vault mount-tune -max-lease-ttl=87600h pki
Successfully tuned mount 'pki'!
```
or
That sets the maximum TTL for secrets issued from the mount to 10 years. (Note
that roles can further restrict the maximum TTL.)
```
$ cat bundle.pem | vault write pki/config/ca pem_bundle="-"
Success! Data written to: pki/config/ca
Now, we generate our root certificate:
```text
$ vault write pki/root/generate/internal common_name=myvault.com ttl=87600h
Key Value
certificate -----BEGIN CERTIFICATE-----
MIIDvTCCAqWgAwIBAgIUAsza+fvOw+Xh9ifYQ0gNN0ruuWcwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wHhcNMTUxMTE5MTYwNDU5WhcNMjUx
MTE2MTYwNDU5WjAWMRQwEgYDVQQDEwtteXZhdWx0LmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMUhH4OLf/sa6GuJONGC/CWLY7nDbfH8jAaCKgqV
eJ81KrmcgP8WPhoFsYHFQEQXQZcrJagwYfm19jYn3CaqrYPbciv9bcWi+ECxZV3x
Hs/YdCFk7KgDGCci37w+cy6fSB943FKJqqVbvPv0odmq6LvgGGgneznvuvkIrOWG
qVDrDdvbEZ01XAyzUQJaaiJXExN+6xm1HcBoypCP8ZjjnXHcFQvw2QBItLRU7iUd
ESFgbrkrSPW3HA6KF0ov2qFMoHTiQ6aM4KaHPmXcFPicugYR9owZfZ4lwWJCqT7j
EkhokaMgHnvyRScuiRZhQm8ppHZoYsqrc3glfEuxGHkS+0cCAwEAAaOCAQEwgf4w
DgYDVR0PAQH/BAQDAgGuMBMGA1UdJQQMMAoGCCsGAQUFBwMJMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLvAbt0eUUOoo7hjKiQM2bRqDKrZMB8GA1UdIwQYMBaA
FLvAbt0eUUOoo7hjKiQM2bRqDKrZMDsGCCsGAQUFBwEBBC8wLTArBggrBgEFBQcw
AoYfaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3BraS9jYTAWBgNVHREEDzANggtt
eXZhdWx0LmNvbTAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vMTI3LjAuMC4xOjgy
MDAvdjEvcGtpL2NybDANBgkqhkiG9w0BAQsFAAOCAQEAVSgIRl6XJs95D7iXGzeQ
Ab8OIei779k0pD7xxS/+knY3TM6733zL/LXs4BEL3wfcQWoDrMtCW0Ook455sAOE
PSnTaZYQSH/F74VawWhSee4ZyiWq+sTUI4IzqYG3IS36mCyb0t6RxEb3aoQ87WHs
BHIB6uWbj6WoGHYM8ESxY89aY9jnX3xSs1HuluVW1uPrpIoa/eudpyV40Y1+9RNM
6fCX5LHGM7vKYxqvudYe+7G1MdKVBQg17h6XuieiUswVt2/HvDlNr+9DHrUla9Ve
Ig43v+grirlG7DrAr6Aiu/MVWKJP6CvNwG/XzrGaqd6KqSsE+8oIGR9tCTuPxI6v
SQ==
-----END CERTIFICATE-----
expiration 1.763309099e+09
issuing_ca -----BEGIN CERTIFICATE-----
MIIDvTCCAqWgAwIBAgIUAsza+fvOw+Xh9ifYQ0gNN0ruuWcwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wHhcNMTUxMTE5MTYwNDU5WhcNMjUx
MTE2MTYwNDU5WjAWMRQwEgYDVQQDEwtteXZhdWx0LmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMUhH4OLf/sa6GuJONGC/CWLY7nDbfH8jAaCKgqV
eJ81KrmcgP8WPhoFsYHFQEQXQZcrJagwYfm19jYn3CaqrYPbciv9bcWi+ECxZV3x
Hs/YdCFk7KgDGCci37w+cy6fSB943FKJqqVbvPv0odmq6LvgGGgneznvuvkIrOWG
qVDrDdvbEZ01XAyzUQJaaiJXExN+6xm1HcBoypCP8ZjjnXHcFQvw2QBItLRU7iUd
ESFgbrkrSPW3HA6KF0ov2qFMoHTiQ6aM4KaHPmXcFPicugYR9owZfZ4lwWJCqT7j
EkhokaMgHnvyRScuiRZhQm8ppHZoYsqrc3glfEuxGHkS+0cCAwEAAaOCAQEwgf4w
DgYDVR0PAQH/BAQDAgGuMBMGA1UdJQQMMAoGCCsGAQUFBwMJMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLvAbt0eUUOoo7hjKiQM2bRqDKrZMB8GA1UdIwQYMBaA
FLvAbt0eUUOoo7hjKiQM2bRqDKrZMDsGCCsGAQUFBwEBBC8wLTArBggrBgEFBQcw
AoYfaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3BraS9jYTAWBgNVHREEDzANggtt
eXZhdWx0LmNvbTAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vMTI3LjAuMC4xOjgy
MDAvdjEvcGtpL2NybDANBgkqhkiG9w0BAQsFAAOCAQEAVSgIRl6XJs95D7iXGzeQ
Ab8OIei779k0pD7xxS/+knY3TM6733zL/LXs4BEL3wfcQWoDrMtCW0Ook455sAOE
PSnTaZYQSH/F74VawWhSee4ZyiWq+sTUI4IzqYG3IS36mCyb0t6RxEb3aoQ87WHs
BHIB6uWbj6WoGHYM8ESxY89aY9jnX3xSs1HuluVW1uPrpIoa/eudpyV40Y1+9RNM
6fCX5LHGM7vKYxqvudYe+7G1MdKVBQg17h6XuieiUswVt2/HvDlNr+9DHrUla9Ve
Ig43v+grirlG7DrAr6Aiu/MVWKJP6CvNwG/XzrGaqd6KqSsE+8oIGR9tCTuPxI6v
SQ==
-----END CERTIFICATE-----
serial_number 02:cc:da:f9:fb:ce:c3:e5:e1:f6:27:d8:43:48:0d:37:4a:ee:b9:67
```
Although in this example the value being piped into *stdin* could be passed
directly into the Vault CLI command, a more complex usage might be to use
[Ansible](http://www.ansible.com) to securely store the certificate and private
key in an `ansible-vault` file, then have an `ansible-playbook` command decrypt
this value and pass it in to Vault.
The returned certificate is purely informational; it and its private key are
safely stored in the backend mount.
#### Set URL configuration
Generated certificates can have the CRL location and the location of the
issuing certificate encoded. These values must be set manually, but can be
changed at any time.
```text
$ vault write pki/config/urls issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"
Success! Data written to: pki/ca/urls
```
#### Configure a role
The next step is to configure a role. A role is a logical name that maps to a
policy used to generated those credentials. For example, let's create an
@ -168,6 +235,8 @@ $ vault write pki/roles/example-dot-com \
Success! Data written to: pki/roles/example-dot-com
```
#### Generate credentials
By writing to the `roles/example-dot-com` path we are defining the
`example-dot-com` role. To generate a new set of credentials, we simply write
to the `issue` endpoint with that role name: Vault is now configured to create
@ -176,30 +245,86 @@ and manage certificates!
```text
$ vault write pki/issue/example-dot-com \
common_name=blah.example.com
Key Value
lease_id pki/issue/example-dot-com/819393b5-e1a1-9efd-b72f-4dc3a1972e31
lease_duration 259200
lease_renewable false
certificate -----BEGIN CERTIFICATE-----
MIIECDCCAvKgAwIBAgIUXmLrLkTdBIOOIYg2/BXO7docKfUwCwYJKoZIhvcNAQEL
...
az3gfwlOqVTdgi/ZVAtIzhSEJ0OY136bq4NOaw==
Key Value
lease_id pki/issue/example-dot-com/32db49a9-61dd-f9ca-f4a6-aaefafe53739
lease_duration 259199
lease_renewable false
EmbeddedPrivateKey map[private_key:-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA760wGT6yzYx85rJZcfGSK59BN2G2GgJGs62VBdXGRFav5Ag1
/nhMqq9Rs/TVm6pNF5iy4zRH+8PI63jQ1jbsMv5+IacJtg+M90GbSC4tGAT2HWdv
8nx+2WfU0lsFm04wiy7xvDMixQmTSNwO+BrqkGZbf/1b6QealQxMih2XIZ/b+Pas
dw9+Bps9i8DsqVkSuuQ+oiCU8mr/qb9LkxgSI2GzN8nD9sCYx8bGPX0ML0zrUX0P
AwHo7ZXFRrV3ZGIrrPPrjPSWZd8hSQuBhkLWKAz9tcyighI4AH93IE7tCLCFljzB
KT4ZTId9Izh+UHiCsZgnHTf8Oc8u9eWJvMnl9QIDAQABAoIBACdHBn7cm4i74Swd
RjM6F242pwVgaetRPCzt4WIXamqAmkqQXejZ5haCrK0FDox4GDIpwAcj5jgIGcEd
B3xTe9nPgvQY36qKWdubUMs/Zfxts7eV3+6pIFCYh3QBnqWY5rOoec0RxDzuGMjz
5zAZmR8Y5x3Oc2V47YhHf7OSrdXAf6IDd4oDvzIO6JoYnTAE6VNBTRSrViyTmJ5c
GTxQQZCroFKY1sg1rxRGEEyl4uRje5CbejEmPugEU6bawRYctNmFt8WZhKhbRx1j
6L8HFvU84ldWgdBUxwk+rAdGHKuAGvObyt6OEu3rDIRGWIyM/c+qfQIARqIcbu2x
JVrRnAECgYEA/cotIg6K/s/v+0zuf+VLcqGPjuRnVYH3IRMbaHxb2z23ojqwiDyF
NO5rIIw8nswvmemECwchGwNd2QyiamNSjczqDgzlZQ4WnqPYvMRe6LqjPRNKevwu
3bH5RK1Whs2347NtE2Hsr2yBjldPCpiBVMDRNWt5wNXwfvel/yMOL8ECgYEA8cOL
0NUhyVk6l++oNE6J7C4NpZZuhnkWNnMPQS8bnAkapQF06/6VOLTovlJtWHPWqzfx
IW4vjr4wBF5X5BSow8TmRVi/JRgYOx75/NhlQzrJ9xI7YZ2RLk4Bu7LlPIbAan9l
mL8w9ggdtfzBY0AUrhFgt//MtZCb04GJK4r6wzUCgYEA7ByomMLG9gIm1rngOgTt
iJxenyZILPlZHeBU44SvQO3OZpQLoPQNSg12hTVzQJnev2bNfiUH28XqDgD/KdN1
/o7iQmglEztkN/tfrV9UnVjmfe/wnzL3tzHZItfRZGEH8EQB+sJkHWDpt6/qzYTM
7jjlarmu8IUZ1kY4nYHTtQECgYB7JDQCAZ089oL+wqY1Rk/ACdEPp6jSO7jRsRdz
BzCT8JsusqhAUCLniFYIIunpJM+R1GOwuHPmy/6fKwKvfMpdNNxpNAPgO/6TlnqU
jBBAByC6BQJT/TLml/sQBNX4V1aIqC0yeSfSGV4wxPYnvIsMAnP2JIe7b1W/jHo2
wIprhQKBgQCRj2D/ddj13lRuHSxDMcrk9P0IQvSK20LO5IMJ6edRxMXsKjuCQ0WR
c4mibLD0DYNoT3fTad9at3QYD0GMuIfzPRpMAd1sIHIQRe/wjQY2zIpAC4yjFW8U
Y/eFKpuvJIT6CPU1lNBZQFc1Td0bKpdTVlJyhOHQMqLrinuSXyWwaw==
-----END RSA PRIVATE KEY----- private_key_type:rsa]
certificate -----BEGIN CERTIFICATE-----
MIIDvzCCAqegAwIBAgIUE5fVBU0TBf6QJ7Z+Vc+0x865S1AwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wHhcNMTUxMTE5MTYxNTMyWhcNMTUx
MTIyMTYxNTMyWjAbMRkwFwYDVQQDExBibGFoLmV4YW1wbGUuY29tMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA760wGT6yzYx85rJZcfGSK59BN2G2GgJG
s62VBdXGRFav5Ag1/nhMqq9Rs/TVm6pNF5iy4zRH+8PI63jQ1jbsMv5+IacJtg+M
90GbSC4tGAT2HWdv8nx+2WfU0lsFm04wiy7xvDMixQmTSNwO+BrqkGZbf/1b6Qea
lQxMih2XIZ/b+Pasdw9+Bps9i8DsqVkSuuQ+oiCU8mr/qb9LkxgSI2GzN8nD9sCY
x8bGPX0ML0zrUX0PAwHo7ZXFRrV3ZGIrrPPrjPSWZd8hSQuBhkLWKAz9tcyighI4
AH93IE7tCLCFljzBKT4ZTId9Izh+UHiCsZgnHTf8Oc8u9eWJvMnl9QIDAQABo4H/
MIH8MA4GA1UdDwEB/wQEAwIDqDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
AwIwHQYDVR0OBBYEFNz/tm08rZmd2QPuBIsuCqdgozSjMB8GA1UdIwQYMBaAFLvA
bt0eUUOoo7hjKiQM2bRqDKrZMDsGCCsGAQUFBwEBBC8wLTArBggrBgEFBQcwAoYf
aHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3BraS9jYTAbBgNVHREEFDASghBibGFo
LmV4YW1wbGUuY29tMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly8xMjcuMC4wLjE6
ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAC+Wbov3HfdZrvrG5i
poODurUkcluxGBXNUjAcXR4IOne14eXszlo7Z3mJY32V4pWliQhYJTuknCC2c6l1
Qgbr+Rw8iYqrap09osLws1vj1vAp6qwcNhjNlZveFBsBtUcgbF6t8JF2P8A11CR6
G/y5jUSXUWTuXf+dD63DSzOlk8+F7ocduXsOLIBTSqQUQhn68zG4JQb020wbbCFI
xHZgEdNedrqo96zhMVnoHbVqm+SnNv6QLHj/2HtSTHA6oICpVcXLtWk+WojViIpl
eeMEKTzLWK53Ijv3bCGJD8YNc/RpzEAKaVUvYgQdSZvzDOfRs2rugYeq/vWH7h18
rySo
-----END CERTIFICATE-----
issuing_ca -----BEGIN CERTIFICATE-----
MIIDUTCCAjmgAwIBAgIJAKM+z4MSfw2mMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
...
issuing_ca -----BEGIN CERTIFICATE-----
MIIDvTCCAqWgAwIBAgIUAsza+fvOw+Xh9ifYQ0gNN0ruuWcwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wHhcNMTUxMTE5MTYwNDU5WhcNMjUx
MTE2MTYwNDU5WjAWMRQwEgYDVQQDEwtteXZhdWx0LmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMUhH4OLf/sa6GuJONGC/CWLY7nDbfH8jAaCKgqV
eJ81KrmcgP8WPhoFsYHFQEQXQZcrJagwYfm19jYn3CaqrYPbciv9bcWi+ECxZV3x
Hs/YdCFk7KgDGCci37w+cy6fSB943FKJqqVbvPv0odmq6LvgGGgneznvuvkIrOWG
qVDrDdvbEZ01XAyzUQJaaiJXExN+6xm1HcBoypCP8ZjjnXHcFQvw2QBItLRU7iUd
ESFgbrkrSPW3HA6KF0ov2qFMoHTiQ6aM4KaHPmXcFPicugYR9owZfZ4lwWJCqT7j
EkhokaMgHnvyRScuiRZhQm8ppHZoYsqrc3glfEuxGHkS+0cCAwEAAaOCAQEwgf4w
DgYDVR0PAQH/BAQDAgGuMBMGA1UdJQQMMAoGCCsGAQUFBwMJMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLvAbt0eUUOoo7hjKiQM2bRqDKrZMB8GA1UdIwQYMBaA
FLvAbt0eUUOoo7hjKiQM2bRqDKrZMDsGCCsGAQUFBwEBBC8wLTArBggrBgEFBQcw
AoYfaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3BraS9jYTAWBgNVHREEDzANggtt
eXZhdWx0LmNvbTAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vMTI3LjAuMC4xOjgy
MDAvdjEvcGtpL2NybDANBgkqhkiG9w0BAQsFAAOCAQEAVSgIRl6XJs95D7iXGzeQ
Ab8OIei779k0pD7xxS/+knY3TM6733zL/LXs4BEL3wfcQWoDrMtCW0Ook455sAOE
PSnTaZYQSH/F74VawWhSee4ZyiWq+sTUI4IzqYG3IS36mCyb0t6RxEb3aoQ87WHs
BHIB6uWbj6WoGHYM8ESxY89aY9jnX3xSs1HuluVW1uPrpIoa/eudpyV40Y1+9RNM
6fCX5LHGM7vKYxqvudYe+7G1MdKVBQg17h6XuieiUswVt2/HvDlNr+9DHrUla9Ve
Ig43v+grirlG7DrAr6Aiu/MVWKJP6CvNwG/XzrGaqd6KqSsE+8oIGR9tCTuPxI6v
SQ==
-----END CERTIFICATE-----
private_key -----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA0cczc7Y2yIu7aD/IaDi23Io+tvvDS9XaXXDUFW1kqd58P83r
...
3xhCNnZ3CMQaM2I48sloVK/XoikMLb5MZwOUQn/V+TrhWP4Lu7qD
-----END RSA PRIVATE KEY-----
serial_number 5e:62:eb:2e:44:dd:04:83:8e:21:88:36:fc:15:ce:ed:da:1c:29:f5
serial_number 13:97:d5:05:4d:13:05:fe:90:27:b6:7e:55:cf:b4:c7:ce:b9:4b:50
```
Note that this is a write, not a read, to allow values to be passed in at
request time.
Vault has now generated a new set of credentials using the `example-dot-com`
role configuration. Here we see the dynamically generated private key and
certificate. The issuing CA certificate is returned as well.
@ -416,6 +541,7 @@ subpath for interactive help output.
</dl>
### /pki/config/urls
#### GET
<dl class="api">
@ -425,7 +551,7 @@ subpath for interactive help output.
</dd>
<dt>Method</dt>
<dd>POST</dd>
<dd>GET</dd>
<dt>URL</dt>
<dd>`/pki/config/urls`</dd>
@ -1035,7 +1161,10 @@ subpath for interactive help output.
overwrite any previously-existing private key and certificate._ If the path
ends with `exported`, the private key will be returned in the response; if
it is `internal` the private key will not be returned and *cannot be
retrieved later*. Distribution points use the values set via `config/urls`.
retrieved later*. Distribution points use the values set via
`config/urls`. <br /><br />Vault does _not_ revoke this certificate (since
it could not sign the CRL with an expired certificate), however, this
endpoint does honor the maximum mount TTL.
</dd>
<dt>Method</dt>