2019-12-07 11:18:22 +00:00
|
|
|
package utils
|
|
|
|
|
2020-03-06 01:38:02 +00:00
|
|
|
import (
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
|
|
|
"fmt"
|
|
|
|
"net/url"
|
2022-04-08 07:38:38 +00:00
|
|
|
"strconv"
|
2020-12-03 05:23:52 +00:00
|
|
|
"strings"
|
2020-05-21 02:20:55 +00:00
|
|
|
"unicode"
|
2022-04-07 00:58:51 +00:00
|
|
|
|
|
|
|
"github.com/valyala/fasthttp"
|
2020-03-06 01:38:02 +00:00
|
|
|
)
|
|
|
|
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
|
|
|
// IsStringAbsURL checks a string can be parsed as a URL and that is IsAbs and if it can't it returns an error
|
|
|
|
// describing why.
|
|
|
|
func IsStringAbsURL(input string) (err error) {
|
2022-09-03 01:51:02 +00:00
|
|
|
parsedURL, err := url.ParseRequestURI(input)
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not parse '%s' as a URL", input)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !parsedURL.IsAbs() {
|
|
|
|
return fmt.Errorf("the url '%s' is not absolute because it doesn't start with a scheme like 'http://' or 'https://'", input)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-21 02:20:55 +00:00
|
|
|
// IsStringAlphaNumeric returns false if any rune in the string is not alpha-numeric.
|
|
|
|
func IsStringAlphaNumeric(input string) bool {
|
|
|
|
for _, r := range input {
|
|
|
|
if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-03-05 04:18:31 +00:00
|
|
|
// IsStringInSlice checks if a single string is in a slice of strings.
|
2021-06-01 04:09:50 +00:00
|
|
|
func IsStringInSlice(needle string, haystack []string) (inSlice bool) {
|
|
|
|
for _, b := range haystack {
|
|
|
|
if b == needle {
|
2019-12-07 11:18:22 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2019-12-07 11:18:22 +00:00
|
|
|
return false
|
|
|
|
}
|
2020-03-06 01:38:02 +00:00
|
|
|
|
2023-01-12 10:57:44 +00:00
|
|
|
// IsStringInSliceF checks if a single string is in a slice of strings using the provided isEqual func.
|
|
|
|
func IsStringInSliceF(needle string, haystack []string, isEqual func(needle, item string) bool) (inSlice bool) {
|
|
|
|
for _, b := range haystack {
|
|
|
|
if isEqual(needle, b) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-08-03 09:55:21 +00:00
|
|
|
// IsStringInSliceSuffix checks if the needle string has one of the suffixes in the haystack.
|
|
|
|
func IsStringInSliceSuffix(needle string, haystack []string) (hasSuffix bool) {
|
|
|
|
for _, straw := range haystack {
|
|
|
|
if strings.HasSuffix(needle, straw) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-03-05 04:18:31 +00:00
|
|
|
// IsStringInSliceFold checks if a single string is in a slice of strings but uses strings.EqualFold to compare them.
|
2021-06-01 04:09:50 +00:00
|
|
|
func IsStringInSliceFold(needle string, haystack []string) (inSlice bool) {
|
|
|
|
for _, b := range haystack {
|
|
|
|
if strings.EqualFold(b, needle) {
|
2021-03-05 04:18:31 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-12-16 01:30:03 +00:00
|
|
|
// IsStringInSliceContains checks if a single string is in an array of strings.
|
2021-06-01 04:09:50 +00:00
|
|
|
func IsStringInSliceContains(needle string, haystack []string) (inSlice bool) {
|
|
|
|
for _, b := range haystack {
|
|
|
|
if strings.Contains(needle, b) {
|
2020-12-16 01:30:03 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-12-01 03:32:58 +00:00
|
|
|
// IsStringSliceContainsAll checks if the haystack contains all strings in the needles.
|
|
|
|
func IsStringSliceContainsAll(needles []string, haystack []string) (inSlice bool) {
|
|
|
|
for _, n := range needles {
|
|
|
|
if !IsStringInSlice(n, haystack) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-12-03 00:04:11 +00:00
|
|
|
// IsStringSliceContainsAny checks if the haystack contains any of the strings in the needles.
|
|
|
|
func IsStringSliceContainsAny(needles []string, haystack []string) (inSlice bool) {
|
2023-03-06 03:58:50 +00:00
|
|
|
return IsStringSliceContainsAnyF(needles, haystack, IsStringInSlice)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsStringSliceContainsAnyF checks if the haystack contains any of the strings in the needles using the isInSlice func.
|
|
|
|
func IsStringSliceContainsAnyF(needles []string, haystack []string, isInSlice func(needle string, haystack []string) bool) (inSlice bool) {
|
2021-12-03 00:04:11 +00:00
|
|
|
for _, n := range needles {
|
2023-03-06 03:58:50 +00:00
|
|
|
if isInSlice(n, haystack) {
|
2021-12-03 00:04:11 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-04-20 21:03:38 +00:00
|
|
|
// SliceString splits a string s into an array with each item being a max of int d
|
|
|
|
// d = denominator, n = numerator, q = quotient, r = remainder.
|
2020-03-06 01:38:02 +00:00
|
|
|
func SliceString(s string, d int) (array []string) {
|
|
|
|
n := len(s)
|
|
|
|
q := n / d
|
|
|
|
r := n % d
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2020-03-06 01:38:02 +00:00
|
|
|
for i := 0; i < q; i++ {
|
|
|
|
array = append(array, s[i*d:i*d+d])
|
|
|
|
if i+1 == q && r != 0 {
|
|
|
|
array = append(array, s[i*d+d:])
|
|
|
|
}
|
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2020-03-06 01:38:02 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-04 22:06:05 +00:00
|
|
|
func isStringSlicesDifferent(a, b []string, method func(s string, b []string) bool) (different bool) {
|
2021-02-02 01:01:46 +00:00
|
|
|
if len(a) != len(b) {
|
|
|
|
return true
|
2020-05-04 19:39:25 +00:00
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2021-02-02 01:01:46 +00:00
|
|
|
for _, s := range a {
|
2021-05-04 22:06:05 +00:00
|
|
|
if !method(s, b) {
|
2020-05-04 19:39:25 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2020-05-04 19:39:25 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-05-04 22:06:05 +00:00
|
|
|
// IsStringSlicesDifferent checks two slices of strings and on the first occurrence of a string item not existing in the
|
|
|
|
// other slice returns true, otherwise returns false.
|
|
|
|
func IsStringSlicesDifferent(a, b []string) (different bool) {
|
|
|
|
return isStringSlicesDifferent(a, b, IsStringInSlice)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsStringSlicesDifferentFold checks two slices of strings and on the first occurrence of a string item not existing in
|
|
|
|
// the other slice (case insensitive) returns true, otherwise returns false.
|
|
|
|
func IsStringSlicesDifferentFold(a, b []string) (different bool) {
|
|
|
|
return isStringSlicesDifferent(a, b, IsStringInSliceFold)
|
|
|
|
}
|
|
|
|
|
2022-04-07 00:58:51 +00:00
|
|
|
// IsURLInSlice returns true if the needle url.URL is in the []url.URL haystack.
|
|
|
|
func IsURLInSlice(needle url.URL, haystack []url.URL) (has bool) {
|
|
|
|
for i := 0; i < len(haystack); i++ {
|
|
|
|
if strings.EqualFold(needle.String(), haystack[i].String()) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// StringSliceFromURLs returns a []string from a []url.URL.
|
|
|
|
func StringSliceFromURLs(urls []url.URL) []string {
|
|
|
|
result := make([]string, len(urls))
|
|
|
|
|
|
|
|
for i := 0; i < len(urls); i++ {
|
|
|
|
result[i] = urls[i].String()
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// URLsFromStringSlice returns a []url.URL from a []string.
|
|
|
|
func URLsFromStringSlice(urls []string) []url.URL {
|
|
|
|
var result []url.URL
|
|
|
|
|
|
|
|
for i := 0; i < len(urls); i++ {
|
|
|
|
u, err := url.Parse(urls[i])
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
result = append(result, *u)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// OriginFromURL returns an origin url.URL given another url.URL.
|
2023-04-13 10:58:18 +00:00
|
|
|
func OriginFromURL(u *url.URL) (origin *url.URL) {
|
|
|
|
return &url.URL{
|
2022-04-07 00:58:51 +00:00
|
|
|
Scheme: u.Scheme,
|
|
|
|
Host: u.Host,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-04 19:39:25 +00:00
|
|
|
// StringSlicesDelta takes a before and after []string and compares them returning a added and removed []string.
|
|
|
|
func StringSlicesDelta(before, after []string) (added, removed []string) {
|
|
|
|
for _, s := range before {
|
|
|
|
if !IsStringInSlice(s, after) {
|
|
|
|
removed = append(removed, s)
|
|
|
|
}
|
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2020-05-04 19:39:25 +00:00
|
|
|
for _, s := range after {
|
|
|
|
if !IsStringInSlice(s, before) {
|
|
|
|
added = append(added, s)
|
|
|
|
}
|
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2020-05-04 19:39:25 +00:00
|
|
|
return added, removed
|
|
|
|
}
|
|
|
|
|
2021-07-22 03:52:37 +00:00
|
|
|
// StringHTMLEscape escapes chars for a HTML body.
|
|
|
|
func StringHTMLEscape(input string) (output string) {
|
|
|
|
return htmlEscaper.Replace(input)
|
|
|
|
}
|
2021-11-11 09:13:32 +00:00
|
|
|
|
2022-04-07 05:33:53 +00:00
|
|
|
// StringJoinDelimitedEscaped joins a string with a specified rune delimiter after escaping any instance of that string
|
|
|
|
// in the string slice. Used with StringSplitDelimitedEscaped.
|
|
|
|
func StringJoinDelimitedEscaped(value []string, delimiter rune) string {
|
|
|
|
escaped := make([]string, len(value))
|
|
|
|
for k, v := range value {
|
|
|
|
escaped[k] = strings.ReplaceAll(v, string(delimiter), "\\"+string(delimiter))
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(escaped, string(delimiter))
|
|
|
|
}
|
|
|
|
|
|
|
|
// StringSplitDelimitedEscaped splits a string with a specified rune delimiter after unescaping any instance of that
|
|
|
|
// string in the string slice that has been escaped. Used with StringJoinDelimitedEscaped.
|
|
|
|
func StringSplitDelimitedEscaped(value string, delimiter rune) (out []string) {
|
|
|
|
var escape bool
|
|
|
|
|
|
|
|
split := strings.FieldsFunc(value, func(r rune) bool {
|
|
|
|
if r == '\\' {
|
|
|
|
escape = !escape
|
|
|
|
} else if escape && r != delimiter {
|
|
|
|
escape = false
|
|
|
|
}
|
|
|
|
|
|
|
|
return !escape && r == delimiter
|
|
|
|
})
|
|
|
|
|
|
|
|
for k, v := range split {
|
|
|
|
split[k] = strings.ReplaceAll(v, "\\"+string(delimiter), string(delimiter))
|
|
|
|
}
|
|
|
|
|
|
|
|
return split
|
|
|
|
}
|
|
|
|
|
2022-04-07 00:58:51 +00:00
|
|
|
// JoinAndCanonicalizeHeaders join header strings by a given sep.
|
|
|
|
func JoinAndCanonicalizeHeaders(sep []byte, headers ...string) (joined []byte) {
|
|
|
|
for i, header := range headers {
|
|
|
|
if i != 0 {
|
|
|
|
joined = append(joined, sep...)
|
|
|
|
}
|
|
|
|
|
|
|
|
joined = fasthttp.AppendNormalizedHeaderKey(joined, header)
|
|
|
|
}
|
|
|
|
|
|
|
|
return joined
|
|
|
|
}
|
|
|
|
|
2022-04-08 07:38:38 +00:00
|
|
|
// IsURLHostComponent returns true if the provided url.URL that was parsed from a string to a url.URL via url.Parse is
|
|
|
|
// just a hostname. This is needed because of the way this function parses such strings.
|
|
|
|
func IsURLHostComponent(u url.URL) (isHostComponent bool) {
|
|
|
|
return u.Path != "" && u.Scheme == "" && u.Host == "" && u.RawPath == "" && u.Opaque == "" &&
|
|
|
|
u.RawQuery == "" && u.Fragment == "" && u.RawFragment == ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsURLHostComponentWithPort returns true if the provided url.URL that was parsed from a string to a url.URL via
|
|
|
|
// url.Parse is just a hostname with a port. This is needed because of the way this function parses such strings.
|
|
|
|
func IsURLHostComponentWithPort(u url.URL) (isHostComponentWithPort bool) {
|
|
|
|
if u.Opaque != "" && u.Scheme != "" && u.Host == "" && u.Path == "" && u.RawPath == "" &&
|
|
|
|
u.RawQuery == "" && u.Fragment == "" && u.RawFragment == "" {
|
|
|
|
_, err := strconv.Atoi(u.Opaque)
|
|
|
|
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|