mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	move client auth plugins
This commit is contained in:
		| @@ -1,20 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2015 The Kubernetes Authors. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| // package jsonpath is a template engine using jsonpath syntax, |  | ||||||
| // which can be seen at http://goessner.net/articles/JsonPath/. |  | ||||||
| // In addition, it has {range} {end} function to iterate list and slice. |  | ||||||
| package jsonpath // import "k8s.io/kubernetes/pkg/util/jsonpath" |  | ||||||
| @@ -1,498 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2015 The Kubernetes Authors. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| package jsonpath |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"reflect" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"k8s.io/kubernetes/third_party/forked/golang/template" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type JSONPath struct { |  | ||||||
| 	name       string |  | ||||||
| 	parser     *Parser |  | ||||||
| 	stack      [][]reflect.Value //push and pop values in different scopes |  | ||||||
| 	cur        []reflect.Value   //current scope values |  | ||||||
| 	beginRange int |  | ||||||
| 	inRange    int |  | ||||||
| 	endRange   int |  | ||||||
|  |  | ||||||
| 	allowMissingKeys bool |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func New(name string) *JSONPath { |  | ||||||
| 	return &JSONPath{ |  | ||||||
| 		name:       name, |  | ||||||
| 		beginRange: 0, |  | ||||||
| 		inRange:    0, |  | ||||||
| 		endRange:   0, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // AllowMissingKeys allows a caller to specify whether they want an error if a field or map key |  | ||||||
| // cannot be located, or simply an empty result. The receiver is returned for chaining. |  | ||||||
| func (j *JSONPath) AllowMissingKeys(allow bool) *JSONPath { |  | ||||||
| 	j.allowMissingKeys = allow |  | ||||||
| 	return j |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Parse parse the given template, return error |  | ||||||
| func (j *JSONPath) Parse(text string) (err error) { |  | ||||||
| 	j.parser, err = Parse(j.name, text) |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Execute bounds data into template and write the result |  | ||||||
| func (j *JSONPath) Execute(wr io.Writer, data interface{}) error { |  | ||||||
| 	fullResults, err := j.FindResults(data) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	for ix := range fullResults { |  | ||||||
| 		if err := j.PrintResults(wr, fullResults[ix]); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) { |  | ||||||
| 	if j.parser == nil { |  | ||||||
| 		return nil, fmt.Errorf("%s is an incomplete jsonpath template", j.name) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	j.cur = []reflect.Value{reflect.ValueOf(data)} |  | ||||||
| 	nodes := j.parser.Root.Nodes |  | ||||||
| 	fullResult := [][]reflect.Value{} |  | ||||||
| 	for i := 0; i < len(nodes); i++ { |  | ||||||
| 		node := nodes[i] |  | ||||||
| 		results, err := j.walk(j.cur, node) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		//encounter an end node, break the current block |  | ||||||
| 		if j.endRange > 0 && j.endRange <= j.inRange { |  | ||||||
| 			j.endRange -= 1 |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		//encounter a range node, start a range loop |  | ||||||
| 		if j.beginRange > 0 { |  | ||||||
| 			j.beginRange -= 1 |  | ||||||
| 			j.inRange += 1 |  | ||||||
| 			for k, value := range results { |  | ||||||
| 				j.parser.Root.Nodes = nodes[i+1:] |  | ||||||
| 				if k == len(results)-1 { |  | ||||||
| 					j.inRange -= 1 |  | ||||||
| 				} |  | ||||||
| 				nextResults, err := j.FindResults(value.Interface()) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return nil, err |  | ||||||
| 				} |  | ||||||
| 				fullResult = append(fullResult, nextResults...) |  | ||||||
| 			} |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		fullResult = append(fullResult, results) |  | ||||||
| 	} |  | ||||||
| 	return fullResult, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // PrintResults write the results into writer |  | ||||||
| func (j *JSONPath) PrintResults(wr io.Writer, results []reflect.Value) error { |  | ||||||
| 	for i, r := range results { |  | ||||||
| 		text, err := j.evalToText(r) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		if i != len(results)-1 { |  | ||||||
| 			text = append(text, ' ') |  | ||||||
| 		} |  | ||||||
| 		if _, err = wr.Write(text); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // walk visits tree rooted at the given node in DFS order |  | ||||||
| func (j *JSONPath) walk(value []reflect.Value, node Node) ([]reflect.Value, error) { |  | ||||||
| 	switch node := node.(type) { |  | ||||||
| 	case *ListNode: |  | ||||||
| 		return j.evalList(value, node) |  | ||||||
| 	case *TextNode: |  | ||||||
| 		return []reflect.Value{reflect.ValueOf(node.Text)}, nil |  | ||||||
| 	case *FieldNode: |  | ||||||
| 		return j.evalField(value, node) |  | ||||||
| 	case *ArrayNode: |  | ||||||
| 		return j.evalArray(value, node) |  | ||||||
| 	case *FilterNode: |  | ||||||
| 		return j.evalFilter(value, node) |  | ||||||
| 	case *IntNode: |  | ||||||
| 		return j.evalInt(value, node) |  | ||||||
| 	case *FloatNode: |  | ||||||
| 		return j.evalFloat(value, node) |  | ||||||
| 	case *WildcardNode: |  | ||||||
| 		return j.evalWildcard(value, node) |  | ||||||
| 	case *RecursiveNode: |  | ||||||
| 		return j.evalRecursive(value, node) |  | ||||||
| 	case *UnionNode: |  | ||||||
| 		return j.evalUnion(value, node) |  | ||||||
| 	case *IdentifierNode: |  | ||||||
| 		return j.evalIdentifier(value, node) |  | ||||||
| 	default: |  | ||||||
| 		return value, fmt.Errorf("unexpected Node %v", node) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalInt evaluates IntNode |  | ||||||
| func (j *JSONPath) evalInt(input []reflect.Value, node *IntNode) ([]reflect.Value, error) { |  | ||||||
| 	result := make([]reflect.Value, len(input)) |  | ||||||
| 	for i := range input { |  | ||||||
| 		result[i] = reflect.ValueOf(node.Value) |  | ||||||
| 	} |  | ||||||
| 	return result, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalFloat evaluates FloatNode |  | ||||||
| func (j *JSONPath) evalFloat(input []reflect.Value, node *FloatNode) ([]reflect.Value, error) { |  | ||||||
| 	result := make([]reflect.Value, len(input)) |  | ||||||
| 	for i := range input { |  | ||||||
| 		result[i] = reflect.ValueOf(node.Value) |  | ||||||
| 	} |  | ||||||
| 	return result, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalList evaluates ListNode |  | ||||||
| func (j *JSONPath) evalList(value []reflect.Value, node *ListNode) ([]reflect.Value, error) { |  | ||||||
| 	var err error |  | ||||||
| 	curValue := value |  | ||||||
| 	for _, node := range node.Nodes { |  | ||||||
| 		curValue, err = j.walk(curValue, node) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return curValue, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return curValue, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalIdentifier evaluates IdentifierNode |  | ||||||
| func (j *JSONPath) evalIdentifier(input []reflect.Value, node *IdentifierNode) ([]reflect.Value, error) { |  | ||||||
| 	results := []reflect.Value{} |  | ||||||
| 	switch node.Name { |  | ||||||
| 	case "range": |  | ||||||
| 		j.stack = append(j.stack, j.cur) |  | ||||||
| 		j.beginRange += 1 |  | ||||||
| 		results = input |  | ||||||
| 	case "end": |  | ||||||
| 		if j.endRange < j.inRange { //inside a loop, break the current block |  | ||||||
| 			j.endRange += 1 |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		// the loop is about to end, pop value and continue the following execution |  | ||||||
| 		if len(j.stack) > 0 { |  | ||||||
| 			j.cur, j.stack = j.stack[len(j.stack)-1], j.stack[:len(j.stack)-1] |  | ||||||
| 		} else { |  | ||||||
| 			return results, fmt.Errorf("not in range, nothing to end") |  | ||||||
| 		} |  | ||||||
| 	default: |  | ||||||
| 		return input, fmt.Errorf("unrecognized identifier %v", node.Name) |  | ||||||
| 	} |  | ||||||
| 	return results, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalArray evaluates ArrayNode |  | ||||||
| func (j *JSONPath) evalArray(input []reflect.Value, node *ArrayNode) ([]reflect.Value, error) { |  | ||||||
| 	result := []reflect.Value{} |  | ||||||
| 	for _, value := range input { |  | ||||||
|  |  | ||||||
| 		value, isNil := template.Indirect(value) |  | ||||||
| 		if isNil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if value.Kind() != reflect.Array && value.Kind() != reflect.Slice { |  | ||||||
| 			return input, fmt.Errorf("%v is not array or slice", value.Type()) |  | ||||||
| 		} |  | ||||||
| 		params := node.Params |  | ||||||
| 		if !params[0].Known { |  | ||||||
| 			params[0].Value = 0 |  | ||||||
| 		} |  | ||||||
| 		if params[0].Value < 0 { |  | ||||||
| 			params[0].Value += value.Len() |  | ||||||
| 		} |  | ||||||
| 		if !params[1].Known { |  | ||||||
| 			params[1].Value = value.Len() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if params[1].Value < 0 { |  | ||||||
| 			params[1].Value += value.Len() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		sliceLength := value.Len() |  | ||||||
| 		if params[1].Value != params[0].Value { // if you're requesting zero elements, allow it through. |  | ||||||
| 			if params[0].Value >= sliceLength { |  | ||||||
| 				return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[0].Value, sliceLength) |  | ||||||
| 			} |  | ||||||
| 			if params[1].Value > sliceLength { |  | ||||||
| 				return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[1].Value-1, sliceLength) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !params[2].Known { |  | ||||||
| 			value = value.Slice(params[0].Value, params[1].Value) |  | ||||||
| 		} else { |  | ||||||
| 			value = value.Slice3(params[0].Value, params[1].Value, params[2].Value) |  | ||||||
| 		} |  | ||||||
| 		for i := 0; i < value.Len(); i++ { |  | ||||||
| 			result = append(result, value.Index(i)) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return result, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalUnion evaluates UnionNode |  | ||||||
| func (j *JSONPath) evalUnion(input []reflect.Value, node *UnionNode) ([]reflect.Value, error) { |  | ||||||
| 	result := []reflect.Value{} |  | ||||||
| 	for _, listNode := range node.Nodes { |  | ||||||
| 		temp, err := j.evalList(input, listNode) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return input, err |  | ||||||
| 		} |  | ||||||
| 		result = append(result, temp...) |  | ||||||
| 	} |  | ||||||
| 	return result, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (j *JSONPath) findFieldInValue(value *reflect.Value, node *FieldNode) (reflect.Value, error) { |  | ||||||
| 	t := value.Type() |  | ||||||
| 	var inlineValue *reflect.Value |  | ||||||
| 	for ix := 0; ix < t.NumField(); ix++ { |  | ||||||
| 		f := t.Field(ix) |  | ||||||
| 		jsonTag := f.Tag.Get("json") |  | ||||||
| 		parts := strings.Split(jsonTag, ",") |  | ||||||
| 		if len(parts) == 0 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if parts[0] == node.Value { |  | ||||||
| 			return value.Field(ix), nil |  | ||||||
| 		} |  | ||||||
| 		if len(parts[0]) == 0 { |  | ||||||
| 			val := value.Field(ix) |  | ||||||
| 			inlineValue = &val |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if inlineValue != nil { |  | ||||||
| 		if inlineValue.Kind() == reflect.Struct { |  | ||||||
| 			// handle 'inline' |  | ||||||
| 			match, err := j.findFieldInValue(inlineValue, node) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return reflect.Value{}, err |  | ||||||
| 			} |  | ||||||
| 			if match.IsValid() { |  | ||||||
| 				return match, nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return value.FieldByName(node.Value), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalField evaluates field of struct or key of map. |  | ||||||
| func (j *JSONPath) evalField(input []reflect.Value, node *FieldNode) ([]reflect.Value, error) { |  | ||||||
| 	results := []reflect.Value{} |  | ||||||
| 	// If there's no input, there's no output |  | ||||||
| 	if len(input) == 0 { |  | ||||||
| 		return results, nil |  | ||||||
| 	} |  | ||||||
| 	for _, value := range input { |  | ||||||
| 		var result reflect.Value |  | ||||||
| 		value, isNil := template.Indirect(value) |  | ||||||
| 		if isNil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if value.Kind() == reflect.Struct { |  | ||||||
| 			var err error |  | ||||||
| 			if result, err = j.findFieldInValue(&value, node); err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 		} else if value.Kind() == reflect.Map { |  | ||||||
| 			mapKeyType := value.Type().Key() |  | ||||||
| 			nodeValue := reflect.ValueOf(node.Value) |  | ||||||
| 			// node value type must be convertible to map key type |  | ||||||
| 			if !nodeValue.Type().ConvertibleTo(mapKeyType) { |  | ||||||
| 				return results, fmt.Errorf("%s is not convertible to %s", nodeValue, mapKeyType) |  | ||||||
| 			} |  | ||||||
| 			result = value.MapIndex(nodeValue.Convert(mapKeyType)) |  | ||||||
| 		} |  | ||||||
| 		if result.IsValid() { |  | ||||||
| 			results = append(results, result) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if len(results) == 0 { |  | ||||||
| 		if j.allowMissingKeys { |  | ||||||
| 			return results, nil |  | ||||||
| 		} |  | ||||||
| 		return results, fmt.Errorf("%s is not found", node.Value) |  | ||||||
| 	} |  | ||||||
| 	return results, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalWildcard extract all contents of the given value |  | ||||||
| func (j *JSONPath) evalWildcard(input []reflect.Value, node *WildcardNode) ([]reflect.Value, error) { |  | ||||||
| 	results := []reflect.Value{} |  | ||||||
| 	for _, value := range input { |  | ||||||
| 		value, isNil := template.Indirect(value) |  | ||||||
| 		if isNil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		kind := value.Kind() |  | ||||||
| 		if kind == reflect.Struct { |  | ||||||
| 			for i := 0; i < value.NumField(); i++ { |  | ||||||
| 				results = append(results, value.Field(i)) |  | ||||||
| 			} |  | ||||||
| 		} else if kind == reflect.Map { |  | ||||||
| 			for _, key := range value.MapKeys() { |  | ||||||
| 				results = append(results, value.MapIndex(key)) |  | ||||||
| 			} |  | ||||||
| 		} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String { |  | ||||||
| 			for i := 0; i < value.Len(); i++ { |  | ||||||
| 				results = append(results, value.Index(i)) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return results, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalRecursive visit the given value recursively and push all of them to result |  | ||||||
| func (j *JSONPath) evalRecursive(input []reflect.Value, node *RecursiveNode) ([]reflect.Value, error) { |  | ||||||
| 	result := []reflect.Value{} |  | ||||||
| 	for _, value := range input { |  | ||||||
| 		results := []reflect.Value{} |  | ||||||
| 		value, isNil := template.Indirect(value) |  | ||||||
| 		if isNil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		kind := value.Kind() |  | ||||||
| 		if kind == reflect.Struct { |  | ||||||
| 			for i := 0; i < value.NumField(); i++ { |  | ||||||
| 				results = append(results, value.Field(i)) |  | ||||||
| 			} |  | ||||||
| 		} else if kind == reflect.Map { |  | ||||||
| 			for _, key := range value.MapKeys() { |  | ||||||
| 				results = append(results, value.MapIndex(key)) |  | ||||||
| 			} |  | ||||||
| 		} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String { |  | ||||||
| 			for i := 0; i < value.Len(); i++ { |  | ||||||
| 				results = append(results, value.Index(i)) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if len(results) != 0 { |  | ||||||
| 			result = append(result, value) |  | ||||||
| 			output, err := j.evalRecursive(results, node) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return result, err |  | ||||||
| 			} |  | ||||||
| 			result = append(result, output...) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return result, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalFilter filter array according to FilterNode |  | ||||||
| func (j *JSONPath) evalFilter(input []reflect.Value, node *FilterNode) ([]reflect.Value, error) { |  | ||||||
| 	results := []reflect.Value{} |  | ||||||
| 	for _, value := range input { |  | ||||||
| 		value, _ = template.Indirect(value) |  | ||||||
|  |  | ||||||
| 		if value.Kind() != reflect.Array && value.Kind() != reflect.Slice { |  | ||||||
| 			return input, fmt.Errorf("%v is not array or slice and cannot be filtered", value) |  | ||||||
| 		} |  | ||||||
| 		for i := 0; i < value.Len(); i++ { |  | ||||||
| 			temp := []reflect.Value{value.Index(i)} |  | ||||||
| 			lefts, err := j.evalList(temp, node.Left) |  | ||||||
|  |  | ||||||
| 			//case exists |  | ||||||
| 			if node.Operator == "exists" { |  | ||||||
| 				if len(lefts) > 0 { |  | ||||||
| 					results = append(results, value.Index(i)) |  | ||||||
| 				} |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if err != nil { |  | ||||||
| 				return input, err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			var left, right interface{} |  | ||||||
| 			if len(lefts) != 1 { |  | ||||||
| 				return input, fmt.Errorf("can only compare one element at a time") |  | ||||||
| 			} |  | ||||||
| 			left = lefts[0].Interface() |  | ||||||
|  |  | ||||||
| 			rights, err := j.evalList(temp, node.Right) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return input, err |  | ||||||
| 			} |  | ||||||
| 			if len(rights) != 1 { |  | ||||||
| 				return input, fmt.Errorf("can only compare one element at a time") |  | ||||||
| 			} |  | ||||||
| 			right = rights[0].Interface() |  | ||||||
|  |  | ||||||
| 			pass := false |  | ||||||
| 			switch node.Operator { |  | ||||||
| 			case "<": |  | ||||||
| 				pass, err = template.Less(left, right) |  | ||||||
| 			case ">": |  | ||||||
| 				pass, err = template.Greater(left, right) |  | ||||||
| 			case "==": |  | ||||||
| 				pass, err = template.Equal(left, right) |  | ||||||
| 			case "!=": |  | ||||||
| 				pass, err = template.NotEqual(left, right) |  | ||||||
| 			case "<=": |  | ||||||
| 				pass, err = template.LessEqual(left, right) |  | ||||||
| 			case ">=": |  | ||||||
| 				pass, err = template.GreaterEqual(left, right) |  | ||||||
| 			default: |  | ||||||
| 				return results, fmt.Errorf("unrecognized filter operator %s", node.Operator) |  | ||||||
| 			} |  | ||||||
| 			if err != nil { |  | ||||||
| 				return results, err |  | ||||||
| 			} |  | ||||||
| 			if pass { |  | ||||||
| 				results = append(results, value.Index(i)) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return results, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // evalToText translates reflect value to corresponding text |  | ||||||
| func (j *JSONPath) evalToText(v reflect.Value) ([]byte, error) { |  | ||||||
| 	iface, ok := template.PrintableValue(v) |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, fmt.Errorf("can't print type %s", v.Type()) |  | ||||||
| 	} |  | ||||||
| 	var buffer bytes.Buffer |  | ||||||
| 	fmt.Fprint(&buffer, iface) |  | ||||||
| 	return buffer.Bytes(), nil |  | ||||||
| } |  | ||||||
| @@ -1,239 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2015 The Kubernetes Authors. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| package jsonpath |  | ||||||
|  |  | ||||||
| import "fmt" |  | ||||||
|  |  | ||||||
| // NodeType identifies the type of a parse tree node. |  | ||||||
| type NodeType int |  | ||||||
|  |  | ||||||
| // Type returns itself and provides an easy default implementation |  | ||||||
| func (t NodeType) Type() NodeType { |  | ||||||
| 	return t |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t NodeType) String() string { |  | ||||||
| 	return NodeTypeName[t] |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	NodeText NodeType = iota |  | ||||||
| 	NodeArray |  | ||||||
| 	NodeList |  | ||||||
| 	NodeField |  | ||||||
| 	NodeIdentifier |  | ||||||
| 	NodeFilter |  | ||||||
| 	NodeInt |  | ||||||
| 	NodeFloat |  | ||||||
| 	NodeWildcard |  | ||||||
| 	NodeRecursive |  | ||||||
| 	NodeUnion |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| var NodeTypeName = map[NodeType]string{ |  | ||||||
| 	NodeText:       "NodeText", |  | ||||||
| 	NodeArray:      "NodeArray", |  | ||||||
| 	NodeList:       "NodeList", |  | ||||||
| 	NodeField:      "NodeField", |  | ||||||
| 	NodeIdentifier: "NodeIdentifier", |  | ||||||
| 	NodeFilter:     "NodeFilter", |  | ||||||
| 	NodeInt:        "NodeInt", |  | ||||||
| 	NodeFloat:      "NodeFloat", |  | ||||||
| 	NodeWildcard:   "NodeWildcard", |  | ||||||
| 	NodeRecursive:  "NodeRecursive", |  | ||||||
| 	NodeUnion:      "NodeUnion", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Node interface { |  | ||||||
| 	Type() NodeType |  | ||||||
| 	String() string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ListNode holds a sequence of nodes. |  | ||||||
| type ListNode struct { |  | ||||||
| 	NodeType |  | ||||||
| 	Nodes []Node // The element nodes in lexical order. |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newList() *ListNode { |  | ||||||
| 	return &ListNode{NodeType: NodeList} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (l *ListNode) append(n Node) { |  | ||||||
| 	l.Nodes = append(l.Nodes, n) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (l *ListNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s", l.Type()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TextNode holds plain text. |  | ||||||
| type TextNode struct { |  | ||||||
| 	NodeType |  | ||||||
| 	Text string // The text; may span newlines. |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newText(text string) *TextNode { |  | ||||||
| 	return &TextNode{NodeType: NodeText, Text: text} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *TextNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s: %s", t.Type(), t.Text) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FieldNode holds field of struct |  | ||||||
| type FieldNode struct { |  | ||||||
| 	NodeType |  | ||||||
| 	Value string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newField(value string) *FieldNode { |  | ||||||
| 	return &FieldNode{NodeType: NodeField, Value: value} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (f *FieldNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s: %s", f.Type(), f.Value) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IdentifierNode holds an identifier |  | ||||||
| type IdentifierNode struct { |  | ||||||
| 	NodeType |  | ||||||
| 	Name string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newIdentifier(value string) *IdentifierNode { |  | ||||||
| 	return &IdentifierNode{ |  | ||||||
| 		NodeType: NodeIdentifier, |  | ||||||
| 		Name:     value, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (f *IdentifierNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s: %s", f.Type(), f.Name) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ParamsEntry holds param information for ArrayNode |  | ||||||
| type ParamsEntry struct { |  | ||||||
| 	Value int |  | ||||||
| 	Known bool //whether the value is known when parse it |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ArrayNode holds start, end, step information for array index selection |  | ||||||
| type ArrayNode struct { |  | ||||||
| 	NodeType |  | ||||||
| 	Params [3]ParamsEntry //start, end, step |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newArray(params [3]ParamsEntry) *ArrayNode { |  | ||||||
| 	return &ArrayNode{ |  | ||||||
| 		NodeType: NodeArray, |  | ||||||
| 		Params:   params, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (a *ArrayNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s: %v", a.Type(), a.Params) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FilterNode holds operand and operator information for filter |  | ||||||
| type FilterNode struct { |  | ||||||
| 	NodeType |  | ||||||
| 	Left     *ListNode |  | ||||||
| 	Right    *ListNode |  | ||||||
| 	Operator string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newFilter(left, right *ListNode, operator string) *FilterNode { |  | ||||||
| 	return &FilterNode{ |  | ||||||
| 		NodeType: NodeFilter, |  | ||||||
| 		Left:     left, |  | ||||||
| 		Right:    right, |  | ||||||
| 		Operator: operator, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (f *FilterNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s: %s %s %s", f.Type(), f.Left, f.Operator, f.Right) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IntNode holds integer value |  | ||||||
| type IntNode struct { |  | ||||||
| 	NodeType |  | ||||||
| 	Value int |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newInt(num int) *IntNode { |  | ||||||
| 	return &IntNode{NodeType: NodeInt, Value: num} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (i *IntNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s: %d", i.Type(), i.Value) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FloatNode holds float value |  | ||||||
| type FloatNode struct { |  | ||||||
| 	NodeType |  | ||||||
| 	Value float64 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newFloat(num float64) *FloatNode { |  | ||||||
| 	return &FloatNode{NodeType: NodeFloat, Value: num} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (i *FloatNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s: %f", i.Type(), i.Value) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // WildcardNode means a wildcard |  | ||||||
| type WildcardNode struct { |  | ||||||
| 	NodeType |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newWildcard() *WildcardNode { |  | ||||||
| 	return &WildcardNode{NodeType: NodeWildcard} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (i *WildcardNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s", i.Type()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // RecursiveNode means a recursive descent operator |  | ||||||
| type RecursiveNode struct { |  | ||||||
| 	NodeType |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newRecursive() *RecursiveNode { |  | ||||||
| 	return &RecursiveNode{NodeType: NodeRecursive} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (r *RecursiveNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s", r.Type()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // UnionNode is union of ListNode |  | ||||||
| type UnionNode struct { |  | ||||||
| 	NodeType |  | ||||||
| 	Nodes []*ListNode |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newUnion(nodes []*ListNode) *UnionNode { |  | ||||||
| 	return &UnionNode{NodeType: NodeUnion, Nodes: nodes} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (u *UnionNode) String() string { |  | ||||||
| 	return fmt.Sprintf("%s", u.Type()) |  | ||||||
| } |  | ||||||
| @@ -1,433 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2015 The Kubernetes Authors. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| package jsonpath |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"regexp" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| 	"unicode" |  | ||||||
| 	"unicode/utf8" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const eof = -1 |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	leftDelim  = "{" |  | ||||||
| 	rightDelim = "}" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type Parser struct { |  | ||||||
| 	Name  string |  | ||||||
| 	Root  *ListNode |  | ||||||
| 	input string |  | ||||||
| 	cur   *ListNode |  | ||||||
| 	pos   int |  | ||||||
| 	start int |  | ||||||
| 	width int |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Parse parsed the given text and return a node Parser. |  | ||||||
| // If an error is encountered, parsing stops and an empty |  | ||||||
| // Parser is returned with the error |  | ||||||
| func Parse(name, text string) (*Parser, error) { |  | ||||||
| 	p := NewParser(name) |  | ||||||
| 	err := p.Parse(text) |  | ||||||
| 	if err != nil { |  | ||||||
| 		p = nil |  | ||||||
| 	} |  | ||||||
| 	return p, err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewParser(name string) *Parser { |  | ||||||
| 	return &Parser{ |  | ||||||
| 		Name: name, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseAction parsed the expression inside delimiter |  | ||||||
| func parseAction(name, text string) (*Parser, error) { |  | ||||||
| 	p, err := Parse(name, fmt.Sprintf("%s%s%s", leftDelim, text, rightDelim)) |  | ||||||
| 	// when error happens, p will be nil, so we need to return here |  | ||||||
| 	if err != nil { |  | ||||||
| 		return p, err |  | ||||||
| 	} |  | ||||||
| 	p.Root = p.Root.Nodes[0].(*ListNode) |  | ||||||
| 	return p, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *Parser) Parse(text string) error { |  | ||||||
| 	p.input = text |  | ||||||
| 	p.Root = newList() |  | ||||||
| 	p.pos = 0 |  | ||||||
| 	return p.parseText(p.Root) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // consumeText return the parsed text since last cosumeText |  | ||||||
| func (p *Parser) consumeText() string { |  | ||||||
| 	value := p.input[p.start:p.pos] |  | ||||||
| 	p.start = p.pos |  | ||||||
| 	return value |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // next returns the next rune in the input. |  | ||||||
| func (p *Parser) next() rune { |  | ||||||
| 	if int(p.pos) >= len(p.input) { |  | ||||||
| 		p.width = 0 |  | ||||||
| 		return eof |  | ||||||
| 	} |  | ||||||
| 	r, w := utf8.DecodeRuneInString(p.input[p.pos:]) |  | ||||||
| 	p.width = w |  | ||||||
| 	p.pos += p.width |  | ||||||
| 	return r |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // peek returns but does not consume the next rune in the input. |  | ||||||
| func (p *Parser) peek() rune { |  | ||||||
| 	r := p.next() |  | ||||||
| 	p.backup() |  | ||||||
| 	return r |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // backup steps back one rune. Can only be called once per call of next. |  | ||||||
| func (p *Parser) backup() { |  | ||||||
| 	p.pos -= p.width |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *Parser) parseText(cur *ListNode) error { |  | ||||||
| 	for { |  | ||||||
| 		if strings.HasPrefix(p.input[p.pos:], leftDelim) { |  | ||||||
| 			if p.pos > p.start { |  | ||||||
| 				cur.append(newText(p.consumeText())) |  | ||||||
| 			} |  | ||||||
| 			return p.parseLeftDelim(cur) |  | ||||||
| 		} |  | ||||||
| 		if p.next() == eof { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// Correctly reached EOF. |  | ||||||
| 	if p.pos > p.start { |  | ||||||
| 		cur.append(newText(p.consumeText())) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseLeftDelim scans the left delimiter, which is known to be present. |  | ||||||
| func (p *Parser) parseLeftDelim(cur *ListNode) error { |  | ||||||
| 	p.pos += len(leftDelim) |  | ||||||
| 	p.consumeText() |  | ||||||
| 	newNode := newList() |  | ||||||
| 	cur.append(newNode) |  | ||||||
| 	cur = newNode |  | ||||||
| 	return p.parseInsideAction(cur) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *Parser) parseInsideAction(cur *ListNode) error { |  | ||||||
| 	prefixMap := map[string]func(*ListNode) error{ |  | ||||||
| 		rightDelim: p.parseRightDelim, |  | ||||||
| 		"[?(":      p.parseFilter, |  | ||||||
| 		"..":       p.parseRecursive, |  | ||||||
| 	} |  | ||||||
| 	for prefix, parseFunc := range prefixMap { |  | ||||||
| 		if strings.HasPrefix(p.input[p.pos:], prefix) { |  | ||||||
| 			return parseFunc(cur) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	switch r := p.next(); { |  | ||||||
| 	case r == eof || isEndOfLine(r): |  | ||||||
| 		return fmt.Errorf("unclosed action") |  | ||||||
| 	case r == ' ': |  | ||||||
| 		p.consumeText() |  | ||||||
| 	case r == '@' || r == '$': //the current object, just pass it |  | ||||||
| 		p.consumeText() |  | ||||||
| 	case r == '[': |  | ||||||
| 		return p.parseArray(cur) |  | ||||||
| 	case r == '"': |  | ||||||
| 		return p.parseQuote(cur) |  | ||||||
| 	case r == '.': |  | ||||||
| 		return p.parseField(cur) |  | ||||||
| 	case r == '+' || r == '-' || unicode.IsDigit(r): |  | ||||||
| 		p.backup() |  | ||||||
| 		return p.parseNumber(cur) |  | ||||||
| 	case isAlphaNumeric(r): |  | ||||||
| 		p.backup() |  | ||||||
| 		return p.parseIdentifier(cur) |  | ||||||
| 	default: |  | ||||||
| 		return fmt.Errorf("unrecognized character in action: %#U", r) |  | ||||||
| 	} |  | ||||||
| 	return p.parseInsideAction(cur) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseRightDelim scans the right delimiter, which is known to be present. |  | ||||||
| func (p *Parser) parseRightDelim(cur *ListNode) error { |  | ||||||
| 	p.pos += len(rightDelim) |  | ||||||
| 	p.consumeText() |  | ||||||
| 	cur = p.Root |  | ||||||
| 	return p.parseText(cur) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseIdentifier scans build-in keywords, like "range" "end" |  | ||||||
| func (p *Parser) parseIdentifier(cur *ListNode) error { |  | ||||||
| 	var r rune |  | ||||||
| 	for { |  | ||||||
| 		r = p.next() |  | ||||||
| 		if isTerminator(r) { |  | ||||||
| 			p.backup() |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	value := p.consumeText() |  | ||||||
| 	cur.append(newIdentifier(value)) |  | ||||||
| 	return p.parseInsideAction(cur) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseRecursive scans the recursive desent operator .. |  | ||||||
| func (p *Parser) parseRecursive(cur *ListNode) error { |  | ||||||
| 	p.pos += len("..") |  | ||||||
| 	p.consumeText() |  | ||||||
| 	cur.append(newRecursive()) |  | ||||||
| 	if r := p.peek(); isAlphaNumeric(r) { |  | ||||||
| 		return p.parseField(cur) |  | ||||||
| 	} |  | ||||||
| 	return p.parseInsideAction(cur) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseNumber scans number |  | ||||||
| func (p *Parser) parseNumber(cur *ListNode) error { |  | ||||||
| 	r := p.peek() |  | ||||||
| 	if r == '+' || r == '-' { |  | ||||||
| 		r = p.next() |  | ||||||
| 	} |  | ||||||
| 	for { |  | ||||||
| 		r = p.next() |  | ||||||
| 		if r != '.' && !unicode.IsDigit(r) { |  | ||||||
| 			p.backup() |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	value := p.consumeText() |  | ||||||
| 	i, err := strconv.Atoi(value) |  | ||||||
| 	if err == nil { |  | ||||||
| 		cur.append(newInt(i)) |  | ||||||
| 		return p.parseInsideAction(cur) |  | ||||||
| 	} |  | ||||||
| 	d, err := strconv.ParseFloat(value, 64) |  | ||||||
| 	if err == nil { |  | ||||||
| 		cur.append(newFloat(d)) |  | ||||||
| 		return p.parseInsideAction(cur) |  | ||||||
| 	} |  | ||||||
| 	return fmt.Errorf("cannot parse number %s", value) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseArray scans array index selection |  | ||||||
| func (p *Parser) parseArray(cur *ListNode) error { |  | ||||||
| Loop: |  | ||||||
| 	for { |  | ||||||
| 		switch p.next() { |  | ||||||
| 		case eof, '\n': |  | ||||||
| 			return fmt.Errorf("unterminated array") |  | ||||||
| 		case ']': |  | ||||||
| 			break Loop |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	text := p.consumeText() |  | ||||||
| 	text = string(text[1 : len(text)-1]) |  | ||||||
| 	if text == "*" { |  | ||||||
| 		text = ":" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	//union operator |  | ||||||
| 	strs := strings.Split(text, ",") |  | ||||||
| 	if len(strs) > 1 { |  | ||||||
| 		union := []*ListNode{} |  | ||||||
| 		for _, str := range strs { |  | ||||||
| 			parser, err := parseAction("union", fmt.Sprintf("[%s]", strings.Trim(str, " "))) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			union = append(union, parser.Root) |  | ||||||
| 		} |  | ||||||
| 		cur.append(newUnion(union)) |  | ||||||
| 		return p.parseInsideAction(cur) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// dict key |  | ||||||
| 	reg := regexp.MustCompile(`^'([^']*)'$`) |  | ||||||
| 	value := reg.FindStringSubmatch(text) |  | ||||||
| 	if value != nil { |  | ||||||
| 		parser, err := parseAction("arraydict", fmt.Sprintf(".%s", value[1])) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		for _, node := range parser.Root.Nodes { |  | ||||||
| 			cur.append(node) |  | ||||||
| 		} |  | ||||||
| 		return p.parseInsideAction(cur) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	//slice operator |  | ||||||
| 	reg = regexp.MustCompile(`^(-?[\d]*)(:-?[\d]*)?(:[\d]*)?$`) |  | ||||||
| 	value = reg.FindStringSubmatch(text) |  | ||||||
| 	if value == nil { |  | ||||||
| 		return fmt.Errorf("invalid array index %s", text) |  | ||||||
| 	} |  | ||||||
| 	value = value[1:] |  | ||||||
| 	params := [3]ParamsEntry{} |  | ||||||
| 	for i := 0; i < 3; i++ { |  | ||||||
| 		if value[i] != "" { |  | ||||||
| 			if i > 0 { |  | ||||||
| 				value[i] = value[i][1:] |  | ||||||
| 			} |  | ||||||
| 			if i > 0 && value[i] == "" { |  | ||||||
| 				params[i].Known = false |  | ||||||
| 			} else { |  | ||||||
| 				var err error |  | ||||||
| 				params[i].Known = true |  | ||||||
| 				params[i].Value, err = strconv.Atoi(value[i]) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return fmt.Errorf("array index %s is not a number", value[i]) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			if i == 1 { |  | ||||||
| 				params[i].Known = true |  | ||||||
| 				params[i].Value = params[0].Value + 1 |  | ||||||
| 			} else { |  | ||||||
| 				params[i].Known = false |  | ||||||
| 				params[i].Value = 0 |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	cur.append(newArray(params)) |  | ||||||
| 	return p.parseInsideAction(cur) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseFilter scans filter inside array selection |  | ||||||
| func (p *Parser) parseFilter(cur *ListNode) error { |  | ||||||
| 	p.pos += len("[?(") |  | ||||||
| 	p.consumeText() |  | ||||||
| Loop: |  | ||||||
| 	for { |  | ||||||
| 		switch p.next() { |  | ||||||
| 		case eof, '\n': |  | ||||||
| 			return fmt.Errorf("unterminated filter") |  | ||||||
| 		case ')': |  | ||||||
| 			break Loop |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if p.next() != ']' { |  | ||||||
| 		return fmt.Errorf("unclosed array expect ]") |  | ||||||
| 	} |  | ||||||
| 	reg := regexp.MustCompile(`^([^!<>=]+)([!<>=]+)(.+?)$`) |  | ||||||
| 	text := p.consumeText() |  | ||||||
| 	text = string(text[:len(text)-2]) |  | ||||||
| 	value := reg.FindStringSubmatch(text) |  | ||||||
| 	if value == nil { |  | ||||||
| 		parser, err := parseAction("text", text) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		cur.append(newFilter(parser.Root, newList(), "exists")) |  | ||||||
| 	} else { |  | ||||||
| 		leftParser, err := parseAction("left", value[1]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		rightParser, err := parseAction("right", value[3]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		cur.append(newFilter(leftParser.Root, rightParser.Root, value[2])) |  | ||||||
| 	} |  | ||||||
| 	return p.parseInsideAction(cur) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseQuote unquotes string inside double quote |  | ||||||
| func (p *Parser) parseQuote(cur *ListNode) error { |  | ||||||
| Loop: |  | ||||||
| 	for { |  | ||||||
| 		switch p.next() { |  | ||||||
| 		case eof, '\n': |  | ||||||
| 			return fmt.Errorf("unterminated quoted string") |  | ||||||
| 		case '"': |  | ||||||
| 			break Loop |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	value := p.consumeText() |  | ||||||
| 	s, err := strconv.Unquote(value) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("unquote string %s error %v", value, err) |  | ||||||
| 	} |  | ||||||
| 	cur.append(newText(s)) |  | ||||||
| 	return p.parseInsideAction(cur) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseField scans a field until a terminator |  | ||||||
| func (p *Parser) parseField(cur *ListNode) error { |  | ||||||
| 	p.consumeText() |  | ||||||
| 	for p.advance() { |  | ||||||
| 	} |  | ||||||
| 	value := p.consumeText() |  | ||||||
| 	if value == "*" { |  | ||||||
| 		cur.append(newWildcard()) |  | ||||||
| 	} else { |  | ||||||
| 		cur.append(newField(strings.Replace(value, "\\", "", -1))) |  | ||||||
| 	} |  | ||||||
| 	return p.parseInsideAction(cur) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // advance scans until next non-escaped terminator |  | ||||||
| func (p *Parser) advance() bool { |  | ||||||
| 	r := p.next() |  | ||||||
| 	if r == '\\' { |  | ||||||
| 		p.next() |  | ||||||
| 	} else if isTerminator(r) { |  | ||||||
| 		p.backup() |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // isTerminator reports whether the input is at valid termination character to appear after an identifier. |  | ||||||
| func isTerminator(r rune) bool { |  | ||||||
| 	if isSpace(r) || isEndOfLine(r) { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	switch r { |  | ||||||
| 	case eof, '.', ',', '[', ']', '$', '@', '{', '}': |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // isSpace reports whether r is a space character. |  | ||||||
| func isSpace(r rune) bool { |  | ||||||
| 	return r == ' ' || r == '\t' |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // isEndOfLine reports whether r is an end-of-line character. |  | ||||||
| func isEndOfLine(r rune) bool { |  | ||||||
| 	return r == '\r' || r == '\n' |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. |  | ||||||
| func isAlphaNumeric(r rune) bool { |  | ||||||
| 	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) |  | ||||||
| } |  | ||||||
| @@ -1,35 +0,0 @@ | |||||||
| package(default_visibility = ["//visibility:public"]) |  | ||||||
|  |  | ||||||
| licenses(["notice"]) |  | ||||||
|  |  | ||||||
| load( |  | ||||||
|     "@io_bazel_rules_go//go:def.bzl", |  | ||||||
|     "go_library", |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_library( |  | ||||||
|     name = "go_default_library", |  | ||||||
|     srcs = ["plugins.go"], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     deps = [ |  | ||||||
|         "//plugin/pkg/client/auth/gcp:go_default_library", |  | ||||||
|         "//plugin/pkg/client/auth/oidc:go_default_library", |  | ||||||
|     ], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "package-srcs", |  | ||||||
|     srcs = glob(["**"]), |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     visibility = ["//visibility:private"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "all-srcs", |  | ||||||
|     srcs = [ |  | ||||||
|         ":package-srcs", |  | ||||||
|         "//plugin/pkg/client/auth/gcp:all-srcs", |  | ||||||
|         "//plugin/pkg/client/auth/oidc:all-srcs", |  | ||||||
|     ], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
| @@ -1,45 +0,0 @@ | |||||||
| package(default_visibility = ["//visibility:public"]) |  | ||||||
|  |  | ||||||
| licenses(["notice"]) |  | ||||||
|  |  | ||||||
| load( |  | ||||||
|     "@io_bazel_rules_go//go:def.bzl", |  | ||||||
|     "go_library", |  | ||||||
|     "go_test", |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_library( |  | ||||||
|     name = "go_default_library", |  | ||||||
|     srcs = ["gcp.go"], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     deps = [ |  | ||||||
|         "//pkg/util/jsonpath:go_default_library", |  | ||||||
|         "//vendor:github.com/golang/glog", |  | ||||||
|         "//vendor:golang.org/x/net/context", |  | ||||||
|         "//vendor:golang.org/x/oauth2", |  | ||||||
|         "//vendor:golang.org/x/oauth2/google", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/util/yaml", |  | ||||||
|         "//vendor:k8s.io/client-go/rest", |  | ||||||
|     ], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_test( |  | ||||||
|     name = "go_default_test", |  | ||||||
|     srcs = ["gcp_test.go"], |  | ||||||
|     library = ":go_default_library", |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     deps = ["//vendor:golang.org/x/oauth2"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "package-srcs", |  | ||||||
|     srcs = glob(["**"]), |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     visibility = ["//visibility:private"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "all-srcs", |  | ||||||
|     srcs = [":package-srcs"], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| assignees: |  | ||||||
|   - cjcullen |  | ||||||
|   - jlowdermilk |  | ||||||
| @@ -1,274 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2016 The Kubernetes Authors. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| package gcp |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os/exec" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/golang/glog" |  | ||||||
| 	"golang.org/x/net/context" |  | ||||||
| 	"golang.org/x/oauth2" |  | ||||||
| 	"golang.org/x/oauth2/google" |  | ||||||
| 	"k8s.io/apimachinery/pkg/util/yaml" |  | ||||||
| 	restclient "k8s.io/client-go/rest" |  | ||||||
| 	"k8s.io/kubernetes/pkg/util/jsonpath" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func init() { |  | ||||||
| 	if err := restclient.RegisterAuthProviderPlugin("gcp", newGCPAuthProvider); err != nil { |  | ||||||
| 		glog.Fatalf("Failed to register gcp auth plugin: %v", err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // gcpAuthProvider is an auth provider plugin that uses GCP credentials to provide |  | ||||||
| // tokens for kubectl to authenticate itself to the apiserver. A sample json config |  | ||||||
| // is provided below with all recognized options described. |  | ||||||
| // |  | ||||||
| // { |  | ||||||
| //   'auth-provider': { |  | ||||||
| //     # Required |  | ||||||
| //     "name": "gcp", |  | ||||||
| // |  | ||||||
| //     'config': { |  | ||||||
| //       # Caching options |  | ||||||
| // |  | ||||||
| //       # Raw string data representing cached access token. |  | ||||||
| //       "access-token": "ya29.CjWdA4GiBPTt", |  | ||||||
| //       # RFC3339Nano expiration timestamp for cached access token. |  | ||||||
| //       "expiry": "2016-10-31 22:31:9.123", |  | ||||||
| // |  | ||||||
| //       # Command execution options |  | ||||||
| //       # These options direct the plugin to execute a specified command and parse |  | ||||||
| //       # token and expiry time from the output of the command. |  | ||||||
| // |  | ||||||
| //       # Command to execute for access token. String is split on whitespace |  | ||||||
| //       # with first field treated as the executable, remaining fields as args. |  | ||||||
| //       # Command output will be parsed as JSON. |  | ||||||
| //       "cmd-path": "/usr/bin/gcloud config config-helper --output=json", |  | ||||||
| // |  | ||||||
| //       # JSONPath to the string field that represents the access token in |  | ||||||
| //       # command output. If omitted, defaults to "{.access_token}". |  | ||||||
| //       "token-key": "{.credential.access_token}", |  | ||||||
| // |  | ||||||
| //       # JSONPath to the string field that represents expiration timestamp |  | ||||||
| //       # of the access token in the command output. If omitted, defaults to |  | ||||||
| //       # "{.token_expiry}" |  | ||||||
| //       "expiry-key": ""{.credential.token_expiry}", |  | ||||||
| // |  | ||||||
| //       # golang reference time in the format that the expiration timestamp uses. |  | ||||||
| //       # If omitted, defaults to time.RFC3339Nano |  | ||||||
| //       "time-fmt": "2006-01-02 15:04:05.999999999" |  | ||||||
| //     } |  | ||||||
| //   } |  | ||||||
| // } |  | ||||||
| // |  | ||||||
| type gcpAuthProvider struct { |  | ||||||
| 	tokenSource oauth2.TokenSource |  | ||||||
| 	persister   restclient.AuthProviderConfigPersister |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { |  | ||||||
| 	cmd, useCmd := gcpConfig["cmd-path"] |  | ||||||
| 	var ts oauth2.TokenSource |  | ||||||
| 	var err error |  | ||||||
| 	if useCmd { |  | ||||||
| 		ts, err = newCmdTokenSource(cmd, gcpConfig["token-key"], gcpConfig["expiry-key"], gcpConfig["time-fmt"]) |  | ||||||
| 	} else { |  | ||||||
| 		ts, err = google.DefaultTokenSource(context.Background(), "https://www.googleapis.com/auth/cloud-platform") |  | ||||||
| 	} |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	cts, err := newCachedTokenSource(gcpConfig["access-token"], gcpConfig["expiry"], persister, ts, gcpConfig) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &gcpAuthProvider{cts, persister}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { |  | ||||||
| 	return &oauth2.Transport{ |  | ||||||
| 		Source: g.tokenSource, |  | ||||||
| 		Base:   rt, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (g *gcpAuthProvider) Login() error { return nil } |  | ||||||
|  |  | ||||||
| type cachedTokenSource struct { |  | ||||||
| 	lk          sync.Mutex |  | ||||||
| 	source      oauth2.TokenSource |  | ||||||
| 	accessToken string |  | ||||||
| 	expiry      time.Time |  | ||||||
| 	persister   restclient.AuthProviderConfigPersister |  | ||||||
| 	cache       map[string]string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newCachedTokenSource(accessToken, expiry string, persister restclient.AuthProviderConfigPersister, ts oauth2.TokenSource, cache map[string]string) (*cachedTokenSource, error) { |  | ||||||
| 	var expiryTime time.Time |  | ||||||
| 	if parsedTime, err := time.Parse(time.RFC3339Nano, expiry); err == nil { |  | ||||||
| 		expiryTime = parsedTime |  | ||||||
| 	} |  | ||||||
| 	if cache == nil { |  | ||||||
| 		cache = make(map[string]string) |  | ||||||
| 	} |  | ||||||
| 	return &cachedTokenSource{ |  | ||||||
| 		source:      ts, |  | ||||||
| 		accessToken: accessToken, |  | ||||||
| 		expiry:      expiryTime, |  | ||||||
| 		persister:   persister, |  | ||||||
| 		cache:       cache, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *cachedTokenSource) Token() (*oauth2.Token, error) { |  | ||||||
| 	tok := t.cachedToken() |  | ||||||
| 	if tok.Valid() && !tok.Expiry.IsZero() { |  | ||||||
| 		return tok, nil |  | ||||||
| 	} |  | ||||||
| 	tok, err := t.source.Token() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	cache := t.update(tok) |  | ||||||
| 	if t.persister != nil { |  | ||||||
| 		if err := t.persister.Persist(cache); err != nil { |  | ||||||
| 			glog.V(4).Infof("Failed to persist token: %v", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return tok, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *cachedTokenSource) cachedToken() *oauth2.Token { |  | ||||||
| 	t.lk.Lock() |  | ||||||
| 	defer t.lk.Unlock() |  | ||||||
| 	return &oauth2.Token{ |  | ||||||
| 		AccessToken: t.accessToken, |  | ||||||
| 		TokenType:   "Bearer", |  | ||||||
| 		Expiry:      t.expiry, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string { |  | ||||||
| 	t.lk.Lock() |  | ||||||
| 	defer t.lk.Unlock() |  | ||||||
| 	t.accessToken = tok.AccessToken |  | ||||||
| 	t.expiry = tok.Expiry |  | ||||||
| 	ret := map[string]string{} |  | ||||||
| 	for k, v := range t.cache { |  | ||||||
| 		ret[k] = v |  | ||||||
| 	} |  | ||||||
| 	ret["access-token"] = t.accessToken |  | ||||||
| 	ret["expiry"] = t.expiry.Format(time.RFC3339Nano) |  | ||||||
| 	return ret |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type commandTokenSource struct { |  | ||||||
| 	cmd       string |  | ||||||
| 	args      []string |  | ||||||
| 	tokenKey  string |  | ||||||
| 	expiryKey string |  | ||||||
| 	timeFmt   string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newCmdTokenSource(cmd, tokenKey, expiryKey, timeFmt string) (*commandTokenSource, error) { |  | ||||||
| 	if len(timeFmt) == 0 { |  | ||||||
| 		timeFmt = time.RFC3339Nano |  | ||||||
| 	} |  | ||||||
| 	if len(tokenKey) == 0 { |  | ||||||
| 		tokenKey = "{.access_token}" |  | ||||||
| 	} |  | ||||||
| 	if len(expiryKey) == 0 { |  | ||||||
| 		expiryKey = "{.token_expiry}" |  | ||||||
| 	} |  | ||||||
| 	fields := strings.Fields(cmd) |  | ||||||
| 	if len(fields) == 0 { |  | ||||||
| 		return nil, fmt.Errorf("missing access token cmd") |  | ||||||
| 	} |  | ||||||
| 	return &commandTokenSource{ |  | ||||||
| 		cmd:       fields[0], |  | ||||||
| 		args:      fields[1:], |  | ||||||
| 		tokenKey:  tokenKey, |  | ||||||
| 		expiryKey: expiryKey, |  | ||||||
| 		timeFmt:   timeFmt, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *commandTokenSource) Token() (*oauth2.Token, error) { |  | ||||||
| 	fullCmd := fmt.Sprintf("%s %s", c.cmd, strings.Join(c.args, " ")) |  | ||||||
| 	cmd := exec.Command(c.cmd, c.args...) |  | ||||||
| 	output, err := cmd.Output() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error executing access token command %q: %v", fullCmd, err) |  | ||||||
| 	} |  | ||||||
| 	token, err := c.parseTokenCmdOutput(output) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error parsing output for access token command %q: %v", fullCmd, err) |  | ||||||
| 	} |  | ||||||
| 	return token, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *commandTokenSource) parseTokenCmdOutput(output []byte) (*oauth2.Token, error) { |  | ||||||
| 	output, err := yaml.ToJSON(output) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	var data interface{} |  | ||||||
| 	if err := json.Unmarshal(output, &data); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	accessToken, err := parseJSONPath(data, "token-key", c.tokenKey) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error parsing token-key %q: %v", c.tokenKey, err) |  | ||||||
| 	} |  | ||||||
| 	expiryStr, err := parseJSONPath(data, "expiry-key", c.expiryKey) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error parsing expiry-key %q: %v", c.expiryKey, err) |  | ||||||
| 	} |  | ||||||
| 	var expiry time.Time |  | ||||||
| 	if t, err := time.Parse(c.timeFmt, expiryStr); err != nil { |  | ||||||
| 		glog.V(4).Infof("Failed to parse token expiry from %s (fmt=%s): %v", expiryStr, c.timeFmt, err) |  | ||||||
| 	} else { |  | ||||||
| 		expiry = t |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return &oauth2.Token{ |  | ||||||
| 		AccessToken: accessToken, |  | ||||||
| 		TokenType:   "Bearer", |  | ||||||
| 		Expiry:      expiry, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func parseJSONPath(input interface{}, name, template string) (string, error) { |  | ||||||
| 	j := jsonpath.New(name) |  | ||||||
| 	buf := new(bytes.Buffer) |  | ||||||
| 	if err := j.Parse(template); err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	if err := j.Execute(buf, input); err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	return buf.String(), nil |  | ||||||
| } |  | ||||||
| @@ -1,211 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2016 The Kubernetes Authors. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| package gcp |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"reflect" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"golang.org/x/oauth2" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func TestCmdTokenSource(t *testing.T) { |  | ||||||
| 	fakeExpiry := time.Date(2016, 10, 31, 22, 31, 9, 123000000, time.UTC) |  | ||||||
| 	customFmt := "2006-01-02 15:04:05.999999999" |  | ||||||
|  |  | ||||||
| 	tests := []struct { |  | ||||||
| 		name                              string |  | ||||||
| 		output                            []byte |  | ||||||
| 		cmd, tokenKey, expiryKey, timeFmt string |  | ||||||
| 		tok                               *oauth2.Token |  | ||||||
| 		expectErr                         error |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			"defaults", |  | ||||||
| 			[]byte(`{ |  | ||||||
|   "access_token": "faketoken", |  | ||||||
|   "token_expiry": "2016-10-31T22:31:09.123000000Z" |  | ||||||
| }`), |  | ||||||
| 			"/fake/cmd/path", "", "", "", |  | ||||||
| 			&oauth2.Token{ |  | ||||||
| 				AccessToken: "faketoken", |  | ||||||
| 				TokenType:   "Bearer", |  | ||||||
| 				Expiry:      fakeExpiry, |  | ||||||
| 			}, |  | ||||||
| 			nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"custom keys", |  | ||||||
| 			[]byte(`{ |  | ||||||
|   "token": "faketoken", |  | ||||||
|   "token_expiry": { |  | ||||||
|     "datetime": "2016-10-31 22:31:09.123" |  | ||||||
|   } |  | ||||||
| }`), |  | ||||||
| 			"/fake/cmd/path", "{.token}", "{.token_expiry.datetime}", customFmt, |  | ||||||
| 			&oauth2.Token{ |  | ||||||
| 				AccessToken: "faketoken", |  | ||||||
| 				TokenType:   "Bearer", |  | ||||||
| 				Expiry:      fakeExpiry, |  | ||||||
| 			}, |  | ||||||
| 			nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"missing cmd", |  | ||||||
| 			nil, |  | ||||||
| 			"", "", "", "", |  | ||||||
| 			nil, |  | ||||||
| 			fmt.Errorf("missing access token cmd"), |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"missing token-key", |  | ||||||
| 			[]byte(`{ |  | ||||||
|   "broken": "faketoken", |  | ||||||
|   "token_expiry": { |  | ||||||
|     "datetime": "2016-10-31 22:31:09.123000000Z" |  | ||||||
|   } |  | ||||||
| }`), |  | ||||||
| 			"/fake/cmd/path", "{.token}", "", "", |  | ||||||
| 			nil, |  | ||||||
| 			fmt.Errorf("error parsing token-key %q", "{.token}"), |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"missing expiry-key", |  | ||||||
| 			[]byte(`{ |  | ||||||
|   "access_token": "faketoken", |  | ||||||
|   "expires": "2016-10-31T22:31:09.123000000Z" |  | ||||||
| }`), |  | ||||||
| 			"/fake/cmd/path", "", "{.expiry}", "", |  | ||||||
| 			nil, |  | ||||||
| 			fmt.Errorf("error parsing expiry-key %q", "{.expiry}"), |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"invalid expiry timestamp", |  | ||||||
| 			[]byte(`{ |  | ||||||
|   "access_token": "faketoken", |  | ||||||
|   "token_expiry": "sometime soon, idk" |  | ||||||
| }`), |  | ||||||
| 			"/fake/cmd/path", "", "", "", |  | ||||||
| 			&oauth2.Token{ |  | ||||||
| 				AccessToken: "faketoken", |  | ||||||
| 				TokenType:   "Bearer", |  | ||||||
| 				Expiry:      time.Time{}, |  | ||||||
| 			}, |  | ||||||
| 			nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"bad JSON", |  | ||||||
| 			[]byte(`{ |  | ||||||
|   "access_token": "faketoken", |  | ||||||
|   "token_expiry": "sometime soon, idk" |  | ||||||
|   ------ |  | ||||||
| `), |  | ||||||
| 			"/fake/cmd", "", "", "", |  | ||||||
| 			nil, |  | ||||||
| 			fmt.Errorf("invalid character '-' after object key:value pair"), |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, tc := range tests { |  | ||||||
| 		ts, err := newCmdTokenSource(tc.cmd, tc.tokenKey, tc.expiryKey, tc.timeFmt) |  | ||||||
| 		if err != nil { |  | ||||||
| 			if !strings.Contains(err.Error(), tc.expectErr.Error()) { |  | ||||||
| 				t.Errorf("%s newCmdTokenSource error: %v, want %v", tc.name, err, tc.expectErr) |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		tok, err := ts.parseTokenCmdOutput(tc.output) |  | ||||||
|  |  | ||||||
| 		if err != tc.expectErr && !strings.Contains(err.Error(), tc.expectErr.Error()) { |  | ||||||
| 			t.Errorf("%s parseCmdTokenSource error: %v, want %v", tc.name, err, tc.expectErr) |  | ||||||
| 		} |  | ||||||
| 		if !reflect.DeepEqual(tok, tc.tok) { |  | ||||||
| 			t.Errorf("%s got token %v, want %v", tc.name, tok, tc.tok) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type fakePersister struct { |  | ||||||
| 	lk    sync.Mutex |  | ||||||
| 	cache map[string]string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (f *fakePersister) Persist(cache map[string]string) error { |  | ||||||
| 	f.lk.Lock() |  | ||||||
| 	defer f.lk.Unlock() |  | ||||||
| 	f.cache = map[string]string{} |  | ||||||
| 	for k, v := range cache { |  | ||||||
| 		f.cache[k] = v |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (f *fakePersister) read() map[string]string { |  | ||||||
| 	ret := map[string]string{} |  | ||||||
| 	f.lk.Lock() |  | ||||||
| 	for k, v := range f.cache { |  | ||||||
| 		ret[k] = v |  | ||||||
| 	} |  | ||||||
| 	return ret |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type fakeTokenSource struct { |  | ||||||
| 	token *oauth2.Token |  | ||||||
| 	err   error |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (f *fakeTokenSource) Token() (*oauth2.Token, error) { |  | ||||||
| 	return f.token, f.err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestCachedTokenSource(t *testing.T) { |  | ||||||
| 	tok := &oauth2.Token{AccessToken: "fakeaccesstoken"} |  | ||||||
| 	persister := &fakePersister{} |  | ||||||
| 	source := &fakeTokenSource{ |  | ||||||
| 		token: tok, |  | ||||||
| 		err:   nil, |  | ||||||
| 	} |  | ||||||
| 	cache := map[string]string{ |  | ||||||
| 		"foo": "bar", |  | ||||||
| 		"baz": "bazinga", |  | ||||||
| 	} |  | ||||||
| 	ts, err := newCachedTokenSource("fakeaccesstoken", "", persister, source, cache) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
| 	var wg sync.WaitGroup |  | ||||||
| 	wg.Add(10) |  | ||||||
| 	for i := 0; i < 10; i++ { |  | ||||||
| 		go func() { |  | ||||||
| 			_, err := ts.Token() |  | ||||||
| 			if err != nil { |  | ||||||
| 				t.Errorf("unexpected error: %s", err) |  | ||||||
| 			} |  | ||||||
| 			wg.Done() |  | ||||||
| 		}() |  | ||||||
| 	} |  | ||||||
| 	wg.Wait() |  | ||||||
| 	cache["access-token"] = "fakeaccesstoken" |  | ||||||
| 	cache["expiry"] = tok.Expiry.Format(time.RFC3339Nano) |  | ||||||
| 	if got := persister.read(); !reflect.DeepEqual(got, cache) { |  | ||||||
| 		t.Errorf("got cache %v, want %v", got, cache) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,48 +0,0 @@ | |||||||
| package(default_visibility = ["//visibility:public"]) |  | ||||||
|  |  | ||||||
| licenses(["notice"]) |  | ||||||
|  |  | ||||||
| load( |  | ||||||
|     "@io_bazel_rules_go//go:def.bzl", |  | ||||||
|     "go_library", |  | ||||||
|     "go_test", |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_library( |  | ||||||
|     name = "go_default_library", |  | ||||||
|     srcs = ["oidc.go"], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     deps = [ |  | ||||||
|         "//vendor:github.com/coreos/go-oidc/jose", |  | ||||||
|         "//vendor:github.com/coreos/go-oidc/oauth2", |  | ||||||
|         "//vendor:github.com/coreos/go-oidc/oidc", |  | ||||||
|         "//vendor:github.com/golang/glog", |  | ||||||
|         "//vendor:k8s.io/client-go/rest", |  | ||||||
|     ], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_test( |  | ||||||
|     name = "go_default_test", |  | ||||||
|     srcs = ["oidc_test.go"], |  | ||||||
|     library = ":go_default_library", |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     deps = [ |  | ||||||
|         "//plugin/pkg/auth/authenticator/token/oidc/testing:go_default_library", |  | ||||||
|         "//vendor:github.com/coreos/go-oidc/jose", |  | ||||||
|         "//vendor:github.com/coreos/go-oidc/key", |  | ||||||
|         "//vendor:github.com/coreos/go-oidc/oauth2", |  | ||||||
|     ], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "package-srcs", |  | ||||||
|     srcs = glob(["**"]), |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     visibility = ["//visibility:private"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "all-srcs", |  | ||||||
|     srcs = [":package-srcs"], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| assignees: |  | ||||||
|   - ericchiang |  | ||||||
| @@ -1,333 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2016 The Kubernetes Authors. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| package oidc |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/coreos/go-oidc/jose" |  | ||||||
| 	"github.com/coreos/go-oidc/oauth2" |  | ||||||
| 	"github.com/coreos/go-oidc/oidc" |  | ||||||
| 	"github.com/golang/glog" |  | ||||||
|  |  | ||||||
| 	restclient "k8s.io/client-go/rest" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	cfgIssuerUrl                = "idp-issuer-url" |  | ||||||
| 	cfgClientID                 = "client-id" |  | ||||||
| 	cfgClientSecret             = "client-secret" |  | ||||||
| 	cfgCertificateAuthority     = "idp-certificate-authority" |  | ||||||
| 	cfgCertificateAuthorityData = "idp-certificate-authority-data" |  | ||||||
| 	cfgExtraScopes              = "extra-scopes" |  | ||||||
| 	cfgIDToken                  = "id-token" |  | ||||||
| 	cfgRefreshToken             = "refresh-token" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func init() { |  | ||||||
| 	if err := restclient.RegisterAuthProviderPlugin("oidc", newOIDCAuthProvider); err != nil { |  | ||||||
| 		glog.Fatalf("Failed to register oidc auth plugin: %v", err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // expiryDelta determines how earlier a token should be considered |  | ||||||
| // expired than its actual expiration time. It is used to avoid late |  | ||||||
| // expirations due to client-server time mismatches. |  | ||||||
| // |  | ||||||
| // NOTE(ericchiang): this is take from golang.org/x/oauth2 |  | ||||||
| const expiryDelta = 10 * time.Second |  | ||||||
|  |  | ||||||
| var cache = newClientCache() |  | ||||||
|  |  | ||||||
| // Like TLS transports, keep a cache of OIDC clients indexed by issuer URL. |  | ||||||
| type clientCache struct { |  | ||||||
| 	mu    sync.RWMutex |  | ||||||
| 	cache map[cacheKey]*oidcAuthProvider |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newClientCache() *clientCache { |  | ||||||
| 	return &clientCache{cache: make(map[cacheKey]*oidcAuthProvider)} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type cacheKey struct { |  | ||||||
| 	// Canonical issuer URL string of the provider. |  | ||||||
| 	issuerURL string |  | ||||||
|  |  | ||||||
| 	clientID     string |  | ||||||
| 	clientSecret string |  | ||||||
|  |  | ||||||
| 	// Don't use CA as cache key because we only add a cache entry if we can connect |  | ||||||
| 	// to the issuer in the first place. A valid CA is a prerequisite. |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *clientCache) getClient(issuer, clientID, clientSecret string) (*oidcAuthProvider, bool) { |  | ||||||
| 	c.mu.RLock() |  | ||||||
| 	defer c.mu.RUnlock() |  | ||||||
| 	client, ok := c.cache[cacheKey{issuer, clientID, clientSecret}] |  | ||||||
| 	return client, ok |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // setClient attempts to put the client in the cache but may return any clients |  | ||||||
| // with the same keys set before. This is so there's only ever one client for a provider. |  | ||||||
| func (c *clientCache) setClient(issuer, clientID, clientSecret string, client *oidcAuthProvider) *oidcAuthProvider { |  | ||||||
| 	c.mu.Lock() |  | ||||||
| 	defer c.mu.Unlock() |  | ||||||
| 	key := cacheKey{issuer, clientID, clientSecret} |  | ||||||
|  |  | ||||||
| 	// If another client has already initialized a client for the given provider we want |  | ||||||
| 	// to use that client instead of the one we're trying to set. This is so all transports |  | ||||||
| 	// share a client and can coordinate around the same mutex when refreshing and writing |  | ||||||
| 	// to the kubeconfig. |  | ||||||
| 	if oldClient, ok := c.cache[key]; ok { |  | ||||||
| 		return oldClient |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	c.cache[key] = client |  | ||||||
| 	return client |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { |  | ||||||
| 	issuer := cfg[cfgIssuerUrl] |  | ||||||
| 	if issuer == "" { |  | ||||||
| 		return nil, fmt.Errorf("Must provide %s", cfgIssuerUrl) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	clientID := cfg[cfgClientID] |  | ||||||
| 	if clientID == "" { |  | ||||||
| 		return nil, fmt.Errorf("Must provide %s", cfgClientID) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	clientSecret := cfg[cfgClientSecret] |  | ||||||
| 	if clientSecret == "" { |  | ||||||
| 		return nil, fmt.Errorf("Must provide %s", cfgClientSecret) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Check cache for existing provider. |  | ||||||
| 	if provider, ok := cache.getClient(issuer, clientID, clientSecret); ok { |  | ||||||
| 		return provider, nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var certAuthData []byte |  | ||||||
| 	var err error |  | ||||||
| 	if cfg[cfgCertificateAuthorityData] != "" { |  | ||||||
| 		certAuthData, err = base64.StdEncoding.DecodeString(cfg[cfgCertificateAuthorityData]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	clientConfig := restclient.Config{ |  | ||||||
| 		TLSClientConfig: restclient.TLSClientConfig{ |  | ||||||
| 			CAFile: cfg[cfgCertificateAuthority], |  | ||||||
| 			CAData: certAuthData, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	trans, err := restclient.TransportFor(&clientConfig) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	hc := &http.Client{Transport: trans} |  | ||||||
|  |  | ||||||
| 	providerCfg, err := oidc.FetchProviderConfig(hc, issuer) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error fetching provider config: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	scopes := strings.Split(cfg[cfgExtraScopes], ",") |  | ||||||
| 	oidcCfg := oidc.ClientConfig{ |  | ||||||
| 		HTTPClient: hc, |  | ||||||
| 		Credentials: oidc.ClientCredentials{ |  | ||||||
| 			ID:     clientID, |  | ||||||
| 			Secret: clientSecret, |  | ||||||
| 		}, |  | ||||||
| 		ProviderConfig: providerCfg, |  | ||||||
| 		Scope:          append(scopes, oidc.DefaultScope...), |  | ||||||
| 	} |  | ||||||
| 	client, err := oidc.NewClient(oidcCfg) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error creating OIDC Client: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	provider := &oidcAuthProvider{ |  | ||||||
| 		client:    &oidcClient{client}, |  | ||||||
| 		cfg:       cfg, |  | ||||||
| 		persister: persister, |  | ||||||
| 		now:       time.Now, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return cache.setClient(issuer, clientID, clientSecret, provider), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type oidcAuthProvider struct { |  | ||||||
| 	// Interface rather than a raw *oidc.Client for testing. |  | ||||||
| 	client OIDCClient |  | ||||||
|  |  | ||||||
| 	// Stubbed out for testing. |  | ||||||
| 	now func() time.Time |  | ||||||
|  |  | ||||||
| 	// Mutex guards persisting to the kubeconfig file and allows synchronized |  | ||||||
| 	// updates to the in-memory config. It also ensures concurrent calls to |  | ||||||
| 	// the RoundTripper only trigger a single refresh request. |  | ||||||
| 	mu        sync.Mutex |  | ||||||
| 	cfg       map[string]string |  | ||||||
| 	persister restclient.AuthProviderConfigPersister |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *oidcAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { |  | ||||||
| 	return &roundTripper{ |  | ||||||
| 		wrapped:  rt, |  | ||||||
| 		provider: p, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *oidcAuthProvider) Login() error { |  | ||||||
| 	return errors.New("not yet implemented") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type OIDCClient interface { |  | ||||||
| 	refreshToken(rt string) (oauth2.TokenResponse, error) |  | ||||||
| 	verifyJWT(jwt *jose.JWT) error |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type roundTripper struct { |  | ||||||
| 	provider *oidcAuthProvider |  | ||||||
| 	wrapped  http.RoundTripper |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { |  | ||||||
| 	token, err := r.provider.idToken() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// shallow copy of the struct |  | ||||||
| 	r2 := new(http.Request) |  | ||||||
| 	*r2 = *req |  | ||||||
| 	// deep copy of the Header so we don't modify the original |  | ||||||
| 	// request's Header (as per RoundTripper contract). |  | ||||||
| 	r2.Header = make(http.Header) |  | ||||||
| 	for k, s := range req.Header { |  | ||||||
| 		r2.Header[k] = s |  | ||||||
| 	} |  | ||||||
| 	r2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) |  | ||||||
|  |  | ||||||
| 	return r.wrapped.RoundTrip(r2) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *oidcAuthProvider) idToken() (string, error) { |  | ||||||
| 	p.mu.Lock() |  | ||||||
| 	defer p.mu.Unlock() |  | ||||||
|  |  | ||||||
| 	if idToken, ok := p.cfg[cfgIDToken]; ok && len(idToken) > 0 { |  | ||||||
| 		valid, err := verifyJWTExpiry(p.now(), idToken) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return "", err |  | ||||||
| 		} |  | ||||||
| 		if valid { |  | ||||||
| 			// If the cached id token is still valid use it. |  | ||||||
| 			return idToken, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Try to request a new token using the refresh token. |  | ||||||
| 	rt, ok := p.cfg[cfgRefreshToken] |  | ||||||
| 	if !ok || len(rt) == 0 { |  | ||||||
| 		return "", errors.New("No valid id-token, and cannot refresh without refresh-token") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	tokens, err := p.client.refreshToken(rt) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", fmt.Errorf("could not refresh token: %v", err) |  | ||||||
| 	} |  | ||||||
| 	jwt, err := jose.ParseJWT(tokens.IDToken) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := p.client.verifyJWT(&jwt); err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Create a new config to persist. |  | ||||||
| 	newCfg := make(map[string]string) |  | ||||||
| 	for key, val := range p.cfg { |  | ||||||
| 		newCfg[key] = val |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if tokens.RefreshToken != "" && tokens.RefreshToken != rt { |  | ||||||
| 		newCfg[cfgRefreshToken] = tokens.RefreshToken |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	newCfg[cfgIDToken] = tokens.IDToken |  | ||||||
| 	if err = p.persister.Persist(newCfg); err != nil { |  | ||||||
| 		return "", fmt.Errorf("could not perist new tokens: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Update the in memory config to reflect the on disk one. |  | ||||||
| 	p.cfg = newCfg |  | ||||||
|  |  | ||||||
| 	return tokens.IDToken, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // oidcClient is the real implementation of the OIDCClient interface, which is |  | ||||||
| // used for testing. |  | ||||||
| type oidcClient struct { |  | ||||||
| 	client *oidc.Client |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (o *oidcClient) refreshToken(rt string) (oauth2.TokenResponse, error) { |  | ||||||
| 	oac, err := o.client.OAuthClient() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return oauth2.TokenResponse{}, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return oac.RequestToken(oauth2.GrantTypeRefreshToken, rt) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (o *oidcClient) verifyJWT(jwt *jose.JWT) error { |  | ||||||
| 	return o.client.VerifyJWT(*jwt) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func verifyJWTExpiry(now time.Time, s string) (valid bool, err error) { |  | ||||||
| 	jwt, err := jose.ParseJWT(s) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false, fmt.Errorf("invalid %q", cfgIDToken) |  | ||||||
| 	} |  | ||||||
| 	claims, err := jwt.Claims() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	exp, ok, err := claims.TimeClaim("exp") |  | ||||||
| 	switch { |  | ||||||
| 	case err != nil: |  | ||||||
| 		return false, fmt.Errorf("failed to parse 'exp' claim: %v", err) |  | ||||||
| 	case !ok: |  | ||||||
| 		return false, errors.New("missing required 'exp' claim") |  | ||||||
| 	case exp.After(now.Add(expiryDelta)): |  | ||||||
| 		return true, nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return false, nil |  | ||||||
| } |  | ||||||
| @@ -1,384 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2016 The Kubernetes Authors. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| package oidc |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"errors" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| 	"path" |  | ||||||
| 	"reflect" |  | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/coreos/go-oidc/jose" |  | ||||||
| 	"github.com/coreos/go-oidc/key" |  | ||||||
| 	"github.com/coreos/go-oidc/oauth2" |  | ||||||
|  |  | ||||||
| 	oidctesting "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc/testing" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func clearCache() { |  | ||||||
| 	cache = newClientCache() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type persister struct{} |  | ||||||
|  |  | ||||||
| // we don't need to actually persist anything because there's no way for us to |  | ||||||
| // read from a persister. |  | ||||||
| func (p *persister) Persist(map[string]string) error { return nil } |  | ||||||
|  |  | ||||||
| type noRefreshOIDCClient struct{} |  | ||||||
|  |  | ||||||
| func (c *noRefreshOIDCClient) refreshToken(rt string) (oauth2.TokenResponse, error) { |  | ||||||
| 	return oauth2.TokenResponse{}, errors.New("alwaysErrOIDCClient: cannot refresh token") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *noRefreshOIDCClient) verifyJWT(jwt *jose.JWT) error { |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type mockOIDCClient struct { |  | ||||||
| 	tokenResponse oauth2.TokenResponse |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *mockOIDCClient) refreshToken(rt string) (oauth2.TokenResponse, error) { |  | ||||||
| 	return c.tokenResponse, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *mockOIDCClient) verifyJWT(jwt *jose.JWT) error { |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestNewOIDCAuthProvider(t *testing.T) { |  | ||||||
| 	tempDir, err := ioutil.TempDir(os.TempDir(), "oidc_test") |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("Cannot make temp dir %v", err) |  | ||||||
| 	} |  | ||||||
| 	cert := path.Join(tempDir, "oidc-cert") |  | ||||||
| 	key := path.Join(tempDir, "oidc-key") |  | ||||||
| 	defer os.RemoveAll(tempDir) |  | ||||||
|  |  | ||||||
| 	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key) |  | ||||||
| 	op := oidctesting.NewOIDCProvider(t, "") |  | ||||||
| 	srv, err := op.ServeTLSWithKeyPair(cert, key) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("Cannot start server %v", err) |  | ||||||
| 	} |  | ||||||
| 	defer srv.Close() |  | ||||||
|  |  | ||||||
| 	certData, err := ioutil.ReadFile(cert) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("Could not read cert bytes %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	makeToken := func(exp time.Time) *jose.JWT { |  | ||||||
| 		jwt, err := jose.NewSignedJWT(jose.Claims(map[string]interface{}{ |  | ||||||
| 			"exp": exp.UTC().Unix(), |  | ||||||
| 		}), op.PrivKey.Signer()) |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Fatalf("Could not create signed JWT %v", err) |  | ||||||
| 		} |  | ||||||
| 		return jwt |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	t0 := time.Now() |  | ||||||
|  |  | ||||||
| 	goodToken := makeToken(t0.Add(time.Hour)).Encode() |  | ||||||
| 	expiredToken := makeToken(t0.Add(-time.Hour)).Encode() |  | ||||||
|  |  | ||||||
| 	tests := []struct { |  | ||||||
| 		name string |  | ||||||
|  |  | ||||||
| 		cfg         map[string]string |  | ||||||
| 		wantInitErr bool |  | ||||||
|  |  | ||||||
| 		client       OIDCClient |  | ||||||
| 		wantCfg      map[string]string |  | ||||||
| 		wantTokenErr bool |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			// A Valid configuration |  | ||||||
| 			name: "no id token and no refresh token", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 				cfgClientSecret:         "client-secret", |  | ||||||
| 			}, |  | ||||||
| 			wantTokenErr: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "valid config with an initial token", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 				cfgClientSecret:         "client-secret", |  | ||||||
| 				cfgIDToken:              goodToken, |  | ||||||
| 			}, |  | ||||||
| 			client: new(noRefreshOIDCClient), |  | ||||||
| 			wantCfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 				cfgClientSecret:         "client-secret", |  | ||||||
| 				cfgIDToken:              goodToken, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "invalid ID token with a refresh token", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 				cfgClientSecret:         "client-secret", |  | ||||||
| 				cfgRefreshToken:         "foo", |  | ||||||
| 				cfgIDToken:              expiredToken, |  | ||||||
| 			}, |  | ||||||
| 			client: &mockOIDCClient{ |  | ||||||
| 				tokenResponse: oauth2.TokenResponse{ |  | ||||||
| 					IDToken: goodToken, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			wantCfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 				cfgClientSecret:         "client-secret", |  | ||||||
| 				cfgRefreshToken:         "foo", |  | ||||||
| 				cfgIDToken:              goodToken, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "invalid ID token with a refresh token, server returns new refresh token", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 				cfgClientSecret:         "client-secret", |  | ||||||
| 				cfgRefreshToken:         "foo", |  | ||||||
| 				cfgIDToken:              expiredToken, |  | ||||||
| 			}, |  | ||||||
| 			client: &mockOIDCClient{ |  | ||||||
| 				tokenResponse: oauth2.TokenResponse{ |  | ||||||
| 					IDToken:      goodToken, |  | ||||||
| 					RefreshToken: "bar", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			wantCfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 				cfgClientSecret:         "client-secret", |  | ||||||
| 				cfgRefreshToken:         "bar", |  | ||||||
| 				cfgIDToken:              goodToken, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "expired token and no refresh otken", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 				cfgClientSecret:         "client-secret", |  | ||||||
| 				cfgIDToken:              expiredToken, |  | ||||||
| 			}, |  | ||||||
| 			wantTokenErr: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "valid base64d ca", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:                srv.URL, |  | ||||||
| 				cfgCertificateAuthorityData: base64.StdEncoding.EncodeToString(certData), |  | ||||||
| 				cfgClientID:                 "client-id", |  | ||||||
| 				cfgClientSecret:             "client-secret", |  | ||||||
| 			}, |  | ||||||
| 			client:       new(noRefreshOIDCClient), |  | ||||||
| 			wantTokenErr: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "missing client ID", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientSecret:         "client-secret", |  | ||||||
| 			}, |  | ||||||
| 			wantInitErr: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "missing client secret", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:            srv.URL, |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 			}, |  | ||||||
| 			wantInitErr: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "missing issuer URL", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgCertificateAuthority: cert, |  | ||||||
| 				cfgClientID:             "client-id", |  | ||||||
| 				cfgClientSecret:         "secret", |  | ||||||
| 			}, |  | ||||||
| 			wantInitErr: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "missing TLS config", |  | ||||||
| 			cfg: map[string]string{ |  | ||||||
| 				cfgIssuerUrl:    srv.URL, |  | ||||||
| 				cfgClientID:     "client-id", |  | ||||||
| 				cfgClientSecret: "secret", |  | ||||||
| 			}, |  | ||||||
| 			wantInitErr: true, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		clearCache() |  | ||||||
|  |  | ||||||
| 		p, err := newOIDCAuthProvider("cluster.example.com", tt.cfg, new(persister)) |  | ||||||
| 		if tt.wantInitErr { |  | ||||||
| 			if err == nil { |  | ||||||
| 				t.Errorf("%s: want non-nil err", tt.name) |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Errorf("%s: unexpected error on newOIDCAuthProvider: %v", tt.name, err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		provider := p.(*oidcAuthProvider) |  | ||||||
| 		provider.client = tt.client |  | ||||||
| 		provider.now = func() time.Time { return t0 } |  | ||||||
|  |  | ||||||
| 		if _, err := provider.idToken(); err != nil { |  | ||||||
| 			if !tt.wantTokenErr { |  | ||||||
| 				t.Errorf("%s: failed to get id token: %v", tt.name, err) |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if tt.wantTokenErr { |  | ||||||
| 			t.Errorf("%s: expected to not get id token: %v", tt.name, err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !reflect.DeepEqual(tt.wantCfg, provider.cfg) { |  | ||||||
| 			t.Errorf("%s: expected config %#v got %#v", tt.name, tt.wantCfg, provider.cfg) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestVerifyJWTExpiry(t *testing.T) { |  | ||||||
| 	privKey, err := key.GeneratePrivateKey() |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("can't generate private key: %v", err) |  | ||||||
| 	} |  | ||||||
| 	makeToken := func(s string, exp time.Time, count int) *jose.JWT { |  | ||||||
| 		jwt, err := jose.NewSignedJWT(jose.Claims(map[string]interface{}{ |  | ||||||
| 			"test":  s, |  | ||||||
| 			"exp":   exp.UTC().Unix(), |  | ||||||
| 			"count": count, |  | ||||||
| 		}), privKey.Signer()) |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Fatalf("Could not create signed JWT %v", err) |  | ||||||
| 		} |  | ||||||
| 		return jwt |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	t0 := time.Now() |  | ||||||
|  |  | ||||||
| 	tests := []struct { |  | ||||||
| 		name        string |  | ||||||
| 		jwt         *jose.JWT |  | ||||||
| 		now         time.Time |  | ||||||
| 		wantErr     bool |  | ||||||
| 		wantExpired bool |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			name: "valid jwt", |  | ||||||
| 			jwt:  makeToken("foo", t0.Add(time.Hour), 1), |  | ||||||
| 			now:  t0, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:    "invalid jwt", |  | ||||||
| 			jwt:     &jose.JWT{}, |  | ||||||
| 			now:     t0, |  | ||||||
| 			wantErr: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:        "expired jwt", |  | ||||||
| 			jwt:         makeToken("foo", t0.Add(-time.Hour), 1), |  | ||||||
| 			now:         t0, |  | ||||||
| 			wantExpired: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:        "jwt expires soon enough to be marked expired", |  | ||||||
| 			jwt:         makeToken("foo", t0, 1), |  | ||||||
| 			now:         t0, |  | ||||||
| 			wantExpired: true, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, tc := range tests { |  | ||||||
| 		func() { |  | ||||||
| 			valid, err := verifyJWTExpiry(tc.now, tc.jwt.Encode()) |  | ||||||
| 			if err != nil { |  | ||||||
| 				if !tc.wantErr { |  | ||||||
| 					t.Errorf("%s: %v", tc.name, err) |  | ||||||
| 				} |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			if tc.wantErr { |  | ||||||
| 				t.Errorf("%s: expected error", tc.name) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if valid && tc.wantExpired { |  | ||||||
| 				t.Errorf("%s: expected token to be expired", tc.name) |  | ||||||
| 			} |  | ||||||
| 			if !valid && !tc.wantExpired { |  | ||||||
| 				t.Errorf("%s: expected token to be valid", tc.name) |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestClientCache(t *testing.T) { |  | ||||||
| 	cache := newClientCache() |  | ||||||
|  |  | ||||||
| 	if _, ok := cache.getClient("issuer1", "id1", "secret1"); ok { |  | ||||||
| 		t.Fatalf("got client before putting one in the cache") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	cli1 := new(oidcAuthProvider) |  | ||||||
| 	cli2 := new(oidcAuthProvider) |  | ||||||
|  |  | ||||||
| 	gotcli := cache.setClient("issuer1", "id1", "secret1", cli1) |  | ||||||
| 	if cli1 != gotcli { |  | ||||||
| 		t.Fatalf("set first client and got a different one") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	gotcli = cache.setClient("issuer1", "id1", "secret1", cli2) |  | ||||||
| 	if cli1 != gotcli { |  | ||||||
| 		t.Fatalf("set a second client and didn't get the first") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2016 The Kubernetes Authors. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| package auth |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	// Initialize all known client auth plugins. |  | ||||||
| 	_ "k8s.io/kubernetes/plugin/pkg/client/auth/gcp" |  | ||||||
| 	_ "k8s.io/kubernetes/plugin/pkg/client/auth/oidc" |  | ||||||
| ) |  | ||||||
| @@ -75,11 +75,14 @@ save "tools/clientcmd/api" | |||||||
| save "rest" | save "rest" | ||||||
| # remove the rest/fake until we're authoritative for it (need to update for registry) | # remove the rest/fake until we're authoritative for it (need to update for registry) | ||||||
| rm -rf ${CLIENT_REPO_TEMP}/rest/fake | rm -rf ${CLIENT_REPO_TEMP}/rest/fake | ||||||
|  | save "pkg/third_party" | ||||||
| save "pkg/util/cert" | save "pkg/util/cert" | ||||||
| save "pkg/util/clock" | save "pkg/util/clock" | ||||||
| save "pkg/util/flowcontrol" | save "pkg/util/flowcontrol" | ||||||
| save "pkg/util/integer" | save "pkg/util/integer" | ||||||
|  | save "pkg/util/jsonpath" | ||||||
| save "pkg/util/testing" | save "pkg/util/testing" | ||||||
|  | save "plugin" | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -105,7 +108,6 @@ mkcp "/pkg/client/unversioned/auth" "/pkg/client/unversioned" | |||||||
| mkcp "/pkg/client/unversioned/clientcmd" "/pkg/client/unversioned" | mkcp "/pkg/client/unversioned/clientcmd" "/pkg/client/unversioned" | ||||||
| mkcp "/pkg/client/unversioned/portforward" "/pkg/client/unversioned" | mkcp "/pkg/client/unversioned/portforward" "/pkg/client/unversioned" | ||||||
|  |  | ||||||
| mkcp "/plugin/pkg/client/auth" "/plugin/pkg/client" |  | ||||||
| mkcp "/pkg/util/workqueue" "pkg/util" | mkcp "/pkg/util/workqueue" "pkg/util" | ||||||
| # remove this folder because it imports prometheus | # remove this folder because it imports prometheus | ||||||
| rm -rf "${CLIENT_REPO_TEMP}/pkg/util/workqueue/prometheus" | rm -rf "${CLIENT_REPO_TEMP}/pkg/util/workqueue/prometheus" | ||||||
| @@ -208,7 +210,6 @@ if [ "$(find "${CLIENT_REPO_TEMP}"/pkg/client -type f -name "*.go")" ]; then | |||||||
| else | else | ||||||
|     rm -r "${CLIENT_REPO_TEMP}"/pkg/client |     rm -r "${CLIENT_REPO_TEMP}"/pkg/client | ||||||
| fi | fi | ||||||
| mvfolder third_party pkg/third_party |  | ||||||
| mvfolder federation pkg/federation | mvfolder federation pkg/federation | ||||||
|  |  | ||||||
| echo "running gofmt" | echo "running gofmt" | ||||||
|   | |||||||
| @@ -17,4 +17,4 @@ limitations under the License. | |||||||
| // package jsonpath is a template engine using jsonpath syntax, | // package jsonpath is a template engine using jsonpath syntax, | ||||||
| // which can be seen at http://goessner.net/articles/JsonPath/. | // which can be seen at http://goessner.net/articles/JsonPath/. | ||||||
| // In addition, it has {range} {end} function to iterate list and slice. | // In addition, it has {range} {end} function to iterate list and slice. | ||||||
| package jsonpath | package jsonpath // import "k8s.io/client-go/pkg/util/jsonpath" | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ import ( | |||||||
| 	"github.com/coreos/go-oidc/key" | 	"github.com/coreos/go-oidc/key" | ||||||
| 	"github.com/coreos/go-oidc/oauth2" | 	"github.com/coreos/go-oidc/oauth2" | ||||||
|  |  | ||||||
| 	oidctesting "k8s.io/client-go/plugin/pkg/auth/authenticator/token/oidc/testing" | 	oidctesting "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc/testing" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func clearCache() { | func clearCache() { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 deads2k
					deads2k