github.com/openfga/openfga@v1.5.4-rc1/internal/condition/types/ipaddress.go (about)

     1  package types
     2  
     3  import (
     4  	"fmt"
     5  	"net/netip"
     6  	"reflect"
     7  
     8  	"github.com/google/cel-go/cel"
     9  	"github.com/google/cel-go/common/types"
    10  	"github.com/google/cel-go/common/types/ref"
    11  	"github.com/google/cel-go/common/types/traits"
    12  	openfgav1 "github.com/openfga/api/proto/openfga/v1"
    13  )
    14  
    15  var ipaddrLibraryDecls = map[string][]cel.FunctionOpt{
    16  	"ipaddress": {
    17  		cel.Overload("string_to_ipaddress", []*cel.Type{cel.StringType}, ipaddrCelType,
    18  			cel.UnaryBinding(stringToIPAddress))},
    19  }
    20  
    21  var ipaddrLib = &IPAddress{}
    22  
    23  func IPAddressEnvOption() cel.EnvOption {
    24  	return cel.Lib(ipaddrLib)
    25  }
    26  
    27  func (ip *IPAddress) CompileOptions() []cel.EnvOption {
    28  	options := []cel.EnvOption{}
    29  	for name, overloads := range ipaddrLibraryDecls {
    30  		options = append(options, cel.Function(name, overloads...))
    31  	}
    32  	return options
    33  }
    34  
    35  func (ip *IPAddress) ProgramOptions() []cel.ProgramOption {
    36  	return []cel.ProgramOption{}
    37  }
    38  
    39  // IPAddressType defines a ParameterType that is used to represent IP addresses in CEL expressions.
    40  var IPAddressType = registerCustomParamType(
    41  	openfgav1.ConditionParamTypeRef_TYPE_NAME_IPADDRESS,
    42  	cel.ObjectType("IPAddress"),
    43  	ipaddressTypeConverterFunc,
    44  	cel.Function("in_cidr",
    45  		cel.MemberOverload("ipaddr_in_cidr",
    46  			[]*cel.Type{cel.ObjectType("IPAddress"), cel.StringType},
    47  			cel.BoolType,
    48  			cel.BinaryBinding(ipaddressCELBinaryBinding),
    49  		),
    50  	),
    51  )
    52  
    53  // IPAddress represents a network IP address.
    54  type IPAddress struct {
    55  	addr netip.Addr
    56  }
    57  
    58  // ParseIPAddress attempts to parse the provided ip string. If the provided string does
    59  // not define a well-formed IP address, then an error is returned.
    60  func ParseIPAddress(ip string) (IPAddress, error) {
    61  	addr, err := netip.ParseAddr(ip)
    62  	if err != nil {
    63  		return IPAddress{}, err
    64  	}
    65  
    66  	return IPAddress{addr}, nil
    67  }
    68  
    69  // ipaddrCelType defines a CEL type for the IPAddress and registers it as a receiver type.
    70  var ipaddrCelType = cel.ObjectType("IPAddress", traits.ReceiverType)
    71  
    72  // ConvertToNative implements the CEL ref.Val.ConvertToNative.
    73  //
    74  // See https://pkg.go.dev/github.com/google/cel-go/common/types/ref#Val
    75  func (ip IPAddress) ConvertToNative(typeDesc reflect.Type) (any, error) {
    76  	if reflect.TypeOf(ip).AssignableTo(typeDesc) {
    77  		return ip, nil
    78  	}
    79  
    80  	switch typeDesc {
    81  	case reflect.TypeOf(""):
    82  		return ip.addr.String(), nil
    83  	default:
    84  		return nil, fmt.Errorf("failed to convert from type '%s' to native Go type 'IPAddress'", typeDesc)
    85  	}
    86  }
    87  
    88  // ConvertToType implements the CEL ref.Val.ConvertToType.
    89  //
    90  // See https://pkg.go.dev/github.com/google/cel-go/common/types/ref#Val
    91  func (ip IPAddress) ConvertToType(typeValue ref.Type) ref.Val {
    92  	switch typeValue {
    93  	case types.StringType:
    94  		return types.String(ip.addr.String())
    95  	case types.TypeType:
    96  		return ipaddrCelType
    97  	default:
    98  		return types.NewErr("failed to convert from CEL type '%s' to '%s'", ipaddrCelType, typeValue)
    99  	}
   100  }
   101  
   102  // Equal implements the CEL ref.Val.Equal.
   103  //
   104  // See https://pkg.go.dev/github.com/google/cel-go/common/types/ref#Val
   105  func (ip IPAddress) Equal(other ref.Val) ref.Val {
   106  	otherip, ok := other.(IPAddress)
   107  	if !ok {
   108  		return types.NoSuchOverloadErr()
   109  	}
   110  
   111  	return types.Bool(ip.addr.Compare(otherip.addr) == 0)
   112  }
   113  
   114  // Type implements the CEL ref.Val.Type.
   115  //
   116  // See https://pkg.go.dev/github.com/google/cel-go/common/types/ref#Val
   117  func (ip IPAddress) Type() ref.Type {
   118  	return ipaddrCelType
   119  }
   120  
   121  // Value implements ref.Val.Value.
   122  //
   123  // See https://pkg.go.dev/github.com/google/cel-go/common/types/ref#Val
   124  func (ip IPAddress) Value() any {
   125  	return ip
   126  }
   127  
   128  // ipaddressBinaryBinding implements a cel.BinaryBinding that is used as a receiver overload for
   129  // comparing an ipaddress value against a network CIDR defined as a string. If the ipaddress is
   130  // within the CIDR range this binding will return true, otherwise it will return false or an error.
   131  //
   132  // See https://pkg.go.dev/github.com/google/cel-go/cel#BinaryBinding
   133  func ipaddressCELBinaryBinding(lhs, rhs ref.Val) ref.Val {
   134  	cidr, ok := rhs.Value().(string)
   135  	if !ok {
   136  		return types.NewErr("a CIDR string is required for comparison")
   137  	}
   138  
   139  	network, err := netip.ParsePrefix(cidr)
   140  	if err != nil {
   141  		return types.NewErr("'%s' is a malformed CIDR string", cidr)
   142  	}
   143  
   144  	ipaddr, ok := lhs.(IPAddress)
   145  	if !ok {
   146  		return types.NewErr("an IPAddress parameter value is required for comparison")
   147  	}
   148  
   149  	return types.Bool(network.Contains(ipaddr.addr))
   150  }
   151  
   152  func stringToIPAddress(arg ref.Val) ref.Val {
   153  	ipStr, ok := arg.Value().(string)
   154  	if !ok {
   155  		return types.MaybeNoSuchOverloadErr(arg)
   156  	}
   157  
   158  	ipaddr, err := ParseIPAddress(ipStr)
   159  	if err != nil {
   160  		return types.NewErr(err.Error())
   161  	}
   162  
   163  	return ipaddr
   164  }