Exemple #1
0
        /// <summary>
        /// Creates the required routes for the IOManager.
        /// </summary>
        /// <param name="handler">A handler to wrap around the call. Takes the output and API input parameters as input.</param>
        /// <returns>The list of API routes</returns>
        internal List <APIRoute> GetRequiredRoutes(Func <dynamic, object[], dynamic> handler = null)
        {
            // TODO: Rethink inputs
            // Might be audio, text, video, image, gesture, etc
            // Will be from a client app, probably not a user directly
            // Audio might output text but can output intent as well
            // Intent needs to be executed and response needs to be
            // negotiated based on the input
            // maybe a ll-accept header / ll-content-type?

            // Inputs can be sent as raw mime byte data or
            // as part of JSON as base64 encoded fields.
            // Here I sent up the processing of the input
            // API route that handles the data sent by the client
            // application
            var routes = new List <APIRoute>();

            var route = new APIRoute
            {
                Path                   = "/input",
                Description            = "Process the input",
                Method                 = HttpMethod.Post,
                Handler                = Handle,
                RequiresAuthentication = true
            };

            routes.Add(route);

            return(routes);
        }
Exemple #2
0
        public void TestRouteMatching()
        {
            var testRoute = new APIRoute(HttpMethod.Get, "/test/{id}");
            var match1    = testRoute.TryMatch(new APIRequest(HttpMethod.Get, "/test/1234"));

            Assert.IsNotNull(match1);
            Assert.IsTrue(match1.ContainsKey("id"));
            Assert.AreEqual(match1["id"], "1234");
        }
Exemple #3
0
 /// <summary>
 /// Adds a route to the API. Prepends '/api' to the path.
 /// </summary>
 /// <param name="route">The route to add</param>
 /// <returns>true if added, false otherwise</returns>
 public bool AddRoute(APIRoute route)
 {
     // Prepending '/api' to the path to ensure API routes
     // don't clash with static routes
     route.Path = $"/api{route.Path}";
     if (_routes.Any(loadedRoute => loadedRoute.Path == route.Path && loadedRoute.Method == route.Method))
     {
         return(false);
     }
     _routes.Add(route);
     return(true);
 }
        /// <summary>
        /// Creates the required routes for all IIdentityProvider implementations.
        /// </summary>
        /// <param name="identityProvider">The provider being extended</param>
        /// <param name="handler">A handler to wrap around the call. Takes the output and API input parameters as input.</param>
        /// <returns>The list of API routes</returns>
        public static List <APIRoute> GetRequiredAPIRoutes(this IIdentityProvider identityProvider, Func <dynamic, object[], dynamic> handler = null)
        {
            var routes = new List <APIRoute>();

            // For the IIdentityProvider interface we need to add routes to the API
            var route = new APIRoute
            {
                Path        = "/login",
                Description = "Log a user in",
                Method      = HttpMethod.Post,
                Handler     = (APIRequest request) =>
                {
                    // RequiredFields property is only implemented on the APIRouteAttribute
                    // so I have to manually do the checks for required interface routes
                    if (request.Data.username == null || request.Data.password == null)
                    {
                        throw new MissingFieldException("Username and password must not be null");
                    }

                    // Try..Catch as I'm calling user code here
                    var apiResponse = new APIResponse();

                    var loginResult = identityProvider.Login((string)request.Data.username, (string)request.Data.password);
                    if (loginResult.IsAuthenticated == false)
                    {
                        apiResponse.StatusCode    = (int)HttpStatusCode.Unauthorized;
                        apiResponse.StatusMessage = "Authentication required";
                        apiResponse.Data          = loginResult.ErrorResponse;
                    }
                    else
                    {
                        apiResponse.Data = loginResult.User;
                    }

                    if (handler == null)
                    {
                        return(apiResponse);
                    }

                    // TODO: the analysis module hook needs to be implemented in a better way
                    // Hiding the password
                    request.Data.password = "******";
                    return(handler(apiResponse, new object[] { request }));
                }
            };

            routes.Add(route);

            return(routes);
        }
Exemple #5
0
        /// <summary>
        /// Constructs the handler function for the route options.
        /// </summary>
        /// <param name="route">The route to build the OPTIONS for</param>
        /// <returns>the OPTIONS response</returns>
        private Func <dynamic, dynamic> BuildRouteOptions(APIRoute route)
        {
            return((dynamic parameters) =>
            {
                var negotiator = Negotiate.WithStatusCode(200);
                negotiator.WithHeader("Access-Control-Allow-Origin", CoreContainer.Instance.Settings.Core.API.CORS.AllowedOrigin);

                // This fetches all the HTTP methods available to the routes
                // that match route.Path and adds it to the response headers
                string methods = string.Join(",", CoreContainer.Instance.RouteManager.GetRoutes()
                                             .Where(x => x.Path == route.Path)
                                             .Select(x => Enum.GetName(typeof(HttpMethod), x.Method).ToUpper()));
                negotiator.WithHeader("Access", methods);
                negotiator.WithHeader("Access-Control-Allow-Methods", methods);

                // Let's allow all the request headers for now
                var headers = new List <string>(Request.Headers.Keys);
                if (Request.Headers.Keys.Contains("Access-Control-Request-Headers"))
                {
                    string acrh = Request.Headers["Access-Control-Request-Headers"].First();
                    if (acrh != null)
                    {
                        headers.AddRange(acrh.Split(','));
                    }
                }
                // and add some ones we know we need
                headers.Add("Accept-Language");
                headers.Add("Request-Language");
                headers.Add("Content-Type");
                negotiator.WithHeader("Access-Control-Allow-Headers", string.Join(",", headers));

                // Always allow credentials
                negotiator.WithHeader("Access-Control-Allow-Credentials", "true");

                // If the route requires auth, then OPTIONS
                // should require it to
                if (route.RequiresAuthentication)
                {
                    this.RequiresAuthentication();
                }
                return negotiator;
            });
        }
        /// <summary>
        /// Gets all the methods marked with <see cref="APIRouteAttribute"/> in the given module
        /// and returns the usable API routes for the module.
        /// </summary>
        /// <param name="module">The module being extended</param>
        /// <param name="handler">A handler to wrap around the module invoke. Takes the output and API input parameters as input.</param>
        /// <returns>The list of API routes</returns>
        public static List <APIRoute> GetAPIRoutes(this IModule module, Func <dynamic, object[], dynamic> handler = null)
        {
            var apiRoutes = new List <APIRoute>();

            // Check for APIRouteAttributes and add the routes that extend the API
            var methods = module.GetType().GetMethods()
                          .Where(m => m.GetCustomAttributes(typeof(APIRouteAttribute), false).Length > 0)
                          .ToArray();

            foreach (MethodInfo methodInfo in methods)
            {
                var attributes = Attribute.GetCustomAttribute(methodInfo, typeof(APIRouteAttribute)) as APIRouteAttribute;
                var route      = new APIRoute
                {
                    Path                   = attributes.Path,
                    Method                 = attributes.Method,
                    Description            = attributes.Description,
                    RequiresAuthentication = attributes.RequiresAuthentication
                };
                route.Handler = (APIRequest request) =>
                {
                    // For Get and Delete I'll only send the parameters
                    // For Post and Put I'll send parameters and postData to the method
                    // For authenticated routes I'll add the user object to the method
                    var invokeParameters = new List <object> {
                        request
                    };
                    if (route.Method == HttpMethod.Post || route.Method == HttpMethod.Put)
                    {
                        // Check if postData contains required fields
                        if (attributes.RequiredFields.Length > 0)
                        {
                            // There are required fields, but postData is null
                            if (request.Data == null)
                            {
                                throw new NullReferenceException("POST data is required for this route");
                            }
                            if (request.Headers.ContentType == MimeType.Json)
                            {
                                IDictionary <string, JToken> lookup = request.Data;
                                foreach (string requiredField in attributes.RequiredFields)
                                {
                                    if (lookup.ContainsKey(requiredField) == false)
                                    {
                                        throw new MissingFieldException($"Required data field '{requiredField}' was not found in the POST data");
                                    }
                                }
                            }
                        }
                    }

                    dynamic result;
                    // TODO: the analysis module hook needs to be implemented in a better way
                    if (handler != null)
                    {
                        // TODO: Ensure we can chain multiple methods
                        result = handler(methodInfo.Invoke(module, invokeParameters.ToArray()), invokeParameters.ToArray());
                    }
                    else
                    {
                        result = (dynamic)methodInfo.Invoke(module, invokeParameters.ToArray());
                    }
                    return(result);
                };
                apiRoutes.Add(route);
            }
            return(apiRoutes);
        }
Exemple #7
0
 public RequestContext(APIRoute route, object contextValue = null)
 {
     this.APIRoute     = route;
     this.ContextValue = contextValue;
 }
Exemple #8
0
        /// <summary>
        /// Constructs the handler function including authentication.
        /// </summary>
        /// <param name="route">The route to build</param>
        /// <returns>The constructed function</returns>
        private Func <dynamic, dynamic> ComposeFunction(APIRoute route)
        {
            return((dynamic parameters) =>
            {
                var request = new APIRequest();
                if (route.RequiresAuthentication)
                {
                    this.RequiresAuthentication();
                    request.AuthenticatedUser = ((InternalUserIdentity)Context.CurrentUser).Meta;
                }

                // Parse the post data, if it's JSON, deserialize into a dynamic
                dynamic postData = Request.Headers.ContentType == MimeType.Json ? JsonConvert.DeserializeObject(Request.Body.AsString()) : Request.Body.AsString();

                // Build the request for API use
                request.Data = postData;
                request.Parameters = parameters;
                request.Headers = CloneHeaders(Request.Headers);

                var negotiator = Negotiate.WithStatusCode(200);

                try
                {
                    var handlerResponse = route.Handler(request);
                    if (handlerResponse is APIResponse)
                    {
                        var apiResponse = handlerResponse as APIResponse;
                        negotiator.WithStatusCode(apiResponse.StatusCode);
                        if (apiResponse.Data != null)
                        {
                            // TODO: Figure out how to have the negotiator work with
                            // any content type returned by the input/output pipelines
                            if (apiResponse.Data is string)
                            {
                                negotiator.WithModel(new Nancy.Responses.TextResponse((string)apiResponse.Data));
                            }
                            else
                            {
                                negotiator.WithModel((object)apiResponse.Data);
                            }
                        }
                        if (apiResponse.StatusMessage != null)
                        {
                            negotiator.WithReasonPhrase(apiResponse.StatusMessage);
                        }
                        if (apiResponse.Headers.Count > 0)
                        {
                            negotiator.WithHeaders(apiResponse.Headers.ToArray());
                        }
                    }
                    else
                    {
                        if (handlerResponse != null)
                        {
                            negotiator.WithModel((object)handlerResponse);
                        }
                    }
                }
                catch (Exception ex)
                {
                    dynamic exceptionResponse = new ExpandoObject();
                    exceptionResponse.Type = ex.GetType();
                    exceptionResponse.Message = ex.Message;
                    exceptionResponse.StackTrace = ex.StackTrace;
                    exceptionResponse.Target = $"{ex.Source}.{ex.TargetSite.Name}";
                    if (ex.InnerException != null)
                    {
                        exceptionResponse.InnerException = ex.InnerException.Message;
                    }
                    // TODO: Fix this part - it breaks the whole point of negotiation.
                    // However, dynamic doesn't work in XML anyway
                    // Perhaps drop negiotiator and just use JSON
                    string exception = JsonConvert.SerializeObject((object)exceptionResponse);
                    negotiator
                    .WithModel(new Nancy.Responses.TextResponse(exception))
                    .WithStatusCode(500)
                    .WithContentType(MimeType.Json);
                }

                return negotiator;
            });
        }
Exemple #9
0
        /// <summary>
        /// Creates the required routes for all IInteractionEngine implementations.
        /// </summary>
        /// <param name="interactionEngine">The <see cref="IInteractionEngine"/> being extended</param>
        /// <param name="handler">A handler to wrap around the call. Takes the output and API input parameters as input.</param>
        /// <returns>The list of API routes</returns>
        public static List <APIRoute> GetRequiredAPIRoutes(this IInteractionEngine interactionEngine, Func <dynamic, object[], dynamic> handler = null)
        {
            var routes = new List <APIRoute>();

            // Create the Skills endpoints
            var route = new APIRoute
            {
                Path                   = "/skills",
                Description            = "List available skills",
                Method                 = HttpMethod.Get,
                RequiresAuthentication = true,
                Handler                = (APIRequest request) =>
                {
                    var response = new APIResponse(interactionEngine.ListSkills());

                    // TODO: the analysis module hook needs to be implemented in a better way
                    return(handler != null?handler(response, new object[] { request }) : response);
                }
            };

            routes.Add(route);

            route = new APIRoute
            {
                Path                   = "/skills",
                Description            = "Register a new skill",
                Method                 = HttpMethod.Post,
                RequiresAuthentication = true,
                Handler                = (APIRequest request) =>
                {
                    Skill skill = ((JObject)request.Data).ToObject <Skill>();
                    switch (skill.Binding)
                    {
                    /*
                     * // TODO: Binary executors can't be loaded over the network using the API
                     * // since they need a method handler
                     * case SkillExecutorBinding.Builtin:
                     * case SkillExecutorBinding.Module:
                     * skill.Executor = ((JObject)skill.Executor).ToObject<BinaryExecutor>();
                     * break;
                     */
                    case SkillExecutorBinding.Network:
                        skill.Executor = ((JObject)skill.Executor).ToObject <NetworkExecutor>();
                        break;

                    default:
                        throw new NotImplementedException(
                                  $"The given skill binding '{skill.Binding}' is not implemented");
                    }

                    APIResponse response = new APIResponse();
                    if (interactionEngine.RegisterSkill(skill))
                    {
                        response.StatusCode = (int)HttpStatusCode.Created;
                        response.Data       = new
                        {
                            UUID       = skill.UUID,
                            Registered = true
                        };
                    }
                    else
                    {
                        response.StatusCode = (int)HttpStatusCode.Conflict;
                        response.Data       = new
                        {
                            Registered = false,
                            Reason     = $"The skill '{skill.UUID}' has already been registered"
                        };
                    }

                    // TODO: the analysis module hook needs to be implemented in a better way
                    return(handler != null?handler(response, new object[] { request }) : response);
                }
            };
            routes.Add(route);

            route = new APIRoute
            {
                Path                   = "/skills/{skillUUID}",
                Description            = "Deregister a new skill",
                Method                 = HttpMethod.Delete,
                RequiresAuthentication = true,
                Handler                = (APIRequest request) =>
                {
                    APIResponse response = new APIResponse();
                    if (interactionEngine.DeregisterSkill(request.Parameters.skillUUID))
                    {
                        response.StatusCode = (int)HttpStatusCode.OK;
                    }
                    else
                    {
                        response.StatusCode    = (int)HttpStatusCode.NotFound;
                        response.StatusMessage =
                            $"The skill '{request.Parameters.skillUUID}' was not found or has already been deregistered";
                    }

                    // TODO: the analysis module hook needs to be implemented in a better way
                    return(handler != null?handler(response, new object[] { request }) : response);
                }
            };
            routes.Add(route);

            return(routes);
        }