/// <summary>
 /// Declare a new shadowed link rel. Shadowed means that the link will be named rel, but use the documentation
 /// from the shadowedRel as the endpoint docs for the new link.
 /// </summary>
 /// <param name="rel">The rel to advertise this link as.</param>
 /// <param name="shadowedRel">The rel on the controller to grab docs from.</param>
 /// <param name="controllerType">The type of the controller to grab docs from.</param>
 /// <param name="routeArgs">The route args.</param>
 public DeclareHalLinkAttribute(string rel, string shadowedRel, Type controllerType, String[] routeArgs = null)
 {
     this.Rel              = rel;
     refInfo               = new HalRelInfo(shadowedRel, controllerType, routeArgs);
     this.GroupName        = HalcyonExtUtils.GetControllerName(refInfo.ControllerType);
     this.UriTemplate      = refInfo.UrlTemplate;
     this.Method           = refInfo.HttpMethod;
     LinkedToControllerRel = true;
 }
 /// <summary>
 /// Declare a new link bound to a controller function.
 /// </summary>
 /// <param name="controllerType">The type of the controller.</param>
 /// <param name="funcName">The name of the function on the controller</param>
 /// <param name="routeArgs">The route args.</param>
 public DeclareHalLinkAttribute(Type controllerType, string funcName, String[] routeArgs = null)
 {
     refInfo               = new HalRelInfo(controllerType, funcName, routeArgs);
     this.Rel              = refInfo.HalRelAttr.Rel;
     this.GroupName        = HalcyonExtUtils.GetControllerName(refInfo.ControllerType);
     this.UriTemplate      = refInfo.UrlTemplate;
     this.Method           = refInfo.HttpMethod;
     LinkedToControllerRel = true;
 }
        public static bool CanUserAccess(ClaimsPrincipal principal, MethodInfo methodInfo, TypeInfo controllerTypeInfo)
        {
            //Check for anon access attribute
            var anonAttrs = methodInfo.GetCustomAttributes <AllowAnonymousAttribute>(true);

            if (anonAttrs.Any())
            {
                return(true);
            }
            //Otherwise check the action method and controller for authorize attributes.
            var attributes = methodInfo.GetCustomAttributes <AuthorizeAttribute>(true);

            attributes = attributes.Concat(controllerTypeInfo.GetCustomAttributes <AuthorizeAttribute>(true));
            return(HalcyonExtUtils.CheckRoles(principal, attributes));
        }
Exemple #4
0
        private void Setup(string rel, Type controllerType, string[] routeArgs, TypeInfo controllerTypeInfo)
        {
            var routeAttr = controllerTypeInfo.GetCustomAttribute <RouteAttribute>();

            if (routeAttr != null)
            {
                this.UrlTemplate += routeAttr.Template;
            }

            routeAttr = this.ActionMethodInfo.GetCustomAttribute <RouteAttribute>();
            if (routeAttr != null)
            {
                EnsureTrailingUrlTemplateSlash();
                this.UrlTemplate += routeAttr.Template;
            }

            var actionMethodName = this.ActionMethodInfo.Name;

            var methodAttribute = this.ActionMethodInfo.GetCustomAttribute <HttpMethodAttribute>();

            if (methodAttribute != null)
            {
                EnsureTrailingUrlTemplateSlash();

                this.UrlTemplate += methodAttribute.Template;
                this.HttpMethod   = methodAttribute.HttpMethods.FirstOrDefault();
            }

            if (this.UrlTemplate.Length == 0)
            {
                throw new InvalidOperationException($"Cannot build a route template for rel {rel} in controller {controllerType}. Did you forget to add a HttpMethod (HttpGet, HttpPost etc) attribute to the Action Method or a RouteAttribute to the controller class or Action Method.");
            }

            //Ensure leading slash
            if (this.UrlTemplate[0] != '\\' && this.UrlTemplate[0] != '/')
            {
                this.UrlTemplate = '/' + this.UrlTemplate;
            }

            //Remove the * from any route variables that include one
            this.UrlTemplate = this.UrlTemplate.Replace("{*", "{").Replace("[controller]", HalcyonExtUtils.GetControllerName(controllerType)).Replace("[action]", actionMethodName);

            if (routeArgs != null)
            {
                foreach (var arg in routeArgs)
                {
                    int firstEquals = arg.IndexOf('=');
                    if (firstEquals != -1)
                    {
                        var key   = $"{{{arg.Substring(0, firstEquals)}}}";
                        var value = arg.Substring(firstEquals + 1).Trim();
                        this.UrlTemplate = this.UrlTemplate.Replace(key, value);
                    }
                    else
                    {
                        throw new InvalidOperationException($"The route argument {arg} for Action Method {actionMethodName} in controller {controllerType} is not valid. It must be in the format key=value.");
                    }
                }
            }

            //Find data mode
            bool isQuery = false;
            bool isBody  = false;
            bool isForm  = false;

            foreach (var arg in this.ActionMethodInfo.GetParameters())
            {
                isQuery = isQuery || arg.GetCustomAttribute <FromQueryAttribute>(true) != null;
                isBody  = isBody || arg.GetCustomAttribute <FromBodyAttribute>(true) != null;
                isForm  = isForm || arg.GetCustomAttribute <FromFormAttribute>(true) != null;
            }

            if (isQuery)
            {
                this.DataMode = DataModes.Query;
            }
            else if (isBody)
            {
                this.DataMode = DataModes.Body;
            }
            else if (isForm)
            {
                this.DataMode = DataModes.Form;
            }
            else
            {
                this.DataMode = DataModes.NoData;
            }
        }
 public bool CanUserAccess(ClaimsPrincipal principal)
 {
     return(HalcyonExtUtils.CanUserAccess(principal, halRefInfo.ActionMethodInfo, halRefInfo.ControllerType.GetTypeInfo()));
 }
        public async Task <EndpointDoc> GetDoc(string groupName, string method, string relativePath, EndpointDocBuilderOptions options)
        {
            if (relativePath == null)
            {
                relativePath = "";
            }
            else if (relativePath.EndsWith("/") || relativePath.EndsWith("\\"))
            {
                relativePath = relativePath.Substring(0, relativePath.Length - 1);
            }

            var group = descriptionProvider.ApiDescriptionGroups.Items.FirstOrDefault(i => i.GroupName == groupName);

            if (group == null)
            {
                throw new InvalidOperationException($"Cannot find an api group for {groupName}. Did you declare a route to that group?");
            }

            var action = group.Items.FirstOrDefault(i => i.HttpMethod == method && i.RelativePath == relativePath);

            if (action == null)
            {
                throw new InvalidOperationException($"Cannot find an api action for {relativePath} and method {method} in api group {groupName}.");
            }

            var  description    = new EndpointDoc();
            bool handleFormData = true;

            if (options.IncludeResponse)
            {
                var controllerActionDesc = action.ActionDescriptor as ControllerActionDescriptor;
                if (controllerActionDesc != null)
                {
                    var methodInfo = controllerActionDesc.MethodInfo;

                    //Check to see if the user can actually access the endpoint we requested
                    if (options.User != null && !HalcyonExtUtils.CanUserAccess(options.User, methodInfo, controllerActionDesc.ControllerTypeInfo))
                    {
                        throw new UnauthorizedAccessException("User cannot access requested endpoint");
                    }

                    var returnType = methodInfo.ReturnType;
                    if (returnType != typeof(void))
                    {
                        description.SetResponseSchema(await endpointDocCache.GetCachedResponse(groupName, method, relativePath, returnType, schemaBuilder.GetSchema));
                    }
                }
            }

            if (options.IncludeRequest)
            {
                foreach (var param in action.ParameterDescriptions)
                {
                    if (param.Source.IsFromRequest)
                    {
                        if (param.Source.CanAcceptDataFrom(BindingSource.Body))
                        {
                            description.SetRequestSchema(await endpointDocCache.GetCachedRequest(groupName, method, relativePath, param.Type, schemaBuilder.GetSchema));
                        }
                        else if (param.Source.CanAcceptDataFrom(BindingSource.Query))
                        {
                            description.SetRequestSchema(await endpointDocCache.GetCachedRequest(groupName, method, relativePath, param.ModelMetadata.ContainerType, schemaBuilder.GetSchema));
                        }
                        else if (handleFormData && param.Source.CanAcceptDataFrom(BindingSource.Form))
                        {
                            handleFormData = false; //This prevents this from running for everything that responsds to form, there should only be 1 per controller method.
                                                    //Discover the type from the action method, there is no way to get the real object type from the description when dealing with form input
                            Type type = null;
                            var  controllerActionDescriptor = action.ActionDescriptor as ControllerActionDescriptor;
                            if (controllerActionDescriptor != null)
                            {
                                foreach (var arg in controllerActionDescriptor.MethodInfo.GetParameters())
                                {
                                    if (arg.CustomAttributes.Any(i => i.AttributeType == typeof(FromFormAttribute)))
                                    {
                                        type = arg.ParameterType;
                                        break;
                                    }
                                }
                            }


                            if (type != null && validSchemaManager.IsValid(type))
                            {
                                description.SetRequestSchema(await endpointDocCache.GetCachedRequest(groupName, method, relativePath, type, async t =>
                                {
                                    var schema = await schemaBuilder.GetSchema(t);
                                    schema.SetDataIsForm(true);
                                    return(schema);
                                }));
                            }
                        }
                    }
                }
            }

            return(description);
        }