1 /**
2  * Methods to generate `security.d`, allowing the client to use the security schemes in the OpenAPI
3  * Specification.
4  */
5 module openapi_client.security;
6 
7 import std.array : appender, split, Appender;
8 import std.file : mkdir, mkdirRecurse, write;
9 import std.path : buildNormalizedPath, dirName;
10 import std.string : tr;
11 import std.stdio : writeln;
12 
13 import openapi : OasDocument, OasServer, OasServerVariable, OasSecurityScheme;
14 import openapi_client.util : wordWrapText, toUpperCamelCase;
15 
16 
17 /**
18  * Writes a utility class containing information about the REST API servers to contact.
19  */
20 void writeSecurityFiles(OasDocument oasDocument, string targetDir, string packageRoot) {
21   auto buffer = appender!string();
22   string moduleName = packageRoot ~ ".security";
23   with (buffer) {
24     put("// File automatically generated from OpenAPI spec.\n");
25     put("module " ~ moduleName ~ ";\n");
26     put("\n");
27     put("import openapi_client.apirequest : ApiRequest;\n");
28     put("import openapi;\n");
29     put("\n");
30     put("import std.base64 : Base64;\n");
31     put("import std.conv : to;\n");
32     put("import std.array : Appender;\n");
33     put("\n");
34     put("class Security {\n\n");
35     put("  /**\n");
36     put("   * The currently active security method to be applied to requests.\n");
37     put("   */\n");
38     put("  static void delegate(ApiRequest) applySecurityF = null;\n\n");
39     foreach (string securitySchemeName, OasSecurityScheme securityScheme;
40         oasDocument.components.securitySchemes) {
41       if (securityScheme.type == "http") {
42         if (securityScheme.scheme == "basic") {
43           put("  /**\n");
44           put("   * Enable and configure the use of HTTP Basic Authentication.\n");
45           put("   *\n");
46           put("   * See_Also: https://en.wikipedia.org/wiki/Basic_access_authentication\n");
47           put("   */\n");
48           put("  static void configure" ~ toUpperCamelCase(securitySchemeName)
49               ~ "(string username, string password) {\n");
50           put("    applySecurityF = (ApiRequest request) {\n");
51           put("      auto buffer = new Appender!string();\n");
52           put("      auto credentials = cast(immutable(ubyte)[])(username ~ \":\" ~ password);\n");
53           put("      Base64.encode(credentials, buffer);\n");
54           put("      request.setHeaderParam(\"Authorization\", \"Basic \" ~ buffer.data);\n");
55           put("    };\n");
56           put("  }\n\n");
57         } else if (securityScheme.scheme == "bearer") {
58           put("  /**\n");
59           put("   * Enable and configure the use of an OAuth2.0 Bearer token for HTTP Authentication.\n");
60           put("   *\n");
61           put("   * See_Also: https://datatracker.ietf.org/doc/html/rfc6750\n");
62           put("   */\n");
63           put("  static void configure" ~ toUpperCamelCase(securitySchemeName)
64               ~ "(string token) {\n");
65           put("    applySecurityF = (ApiRequest request) {\n");
66           put("      request.setHeaderParam(\"Authorization\", \"Bearer \" ~ token);\n");
67           put("    };\n");
68           put("  }\n\n");
69         } else {
70           writeln("Warning: Unsupported security scheme type=http, scheme=", securityScheme.scheme);
71         }
72       } else if (securityScheme.type == "apiKey") {
73         if (!securityScheme.in_ || !securityScheme.name) {
74           throw new Exception("SecurityScheme '" ~ securitySchemeName
75               ~ "', is missing required parameter 'in' or 'name'.");
76         }
77         put("  /**\n");
78         put("   * Enable and configure an API Key for authentication.\n");
79         put("   *\n");
80         put("   * See_Also: https://swagger.io/specification/#security-scheme-object\n");
81         put("   */\n");
82         put("  static void configure" ~ toUpperCamelCase(securitySchemeName)
83             ~ "(string apiKey) {\n");
84         put("    applySecurityF = (ApiRequest request) {\n");
85         if (securityScheme.in_ == "header") {
86           put("      request.setHeaderParam(\"" ~ securityScheme.name ~ "\", apiKey);\n");
87         } else if (securityScheme.in_ == "query") {
88           put("      request.setQueryParam(\"" ~ securityScheme.name ~ "\", apiKey);\n");
89         } else {
90           put("      throw new Exception(\"Security apiKey unsupported in: \" ~ securityScheme.in_);\n");
91         }
92         put("    };\n");
93         put("  }\n\n");
94       } else {
95         writeln("Warning: Unsupported security scheme type '", securityScheme.type, "'.");
96       }
97     }
98     put("  /**\n");
99     put("   * Apply the currently selected security method to a request.\n");
100     put("   */\n");
101     put("  static void apply(ApiRequest request) {\n");
102     put("    if (applySecurityF !is null)\n");
103     put("      applySecurityF(request);\n");
104     put("  }\n\n");
105     put("}\n");
106   }
107   string fileName = buildNormalizedPath(targetDir, tr(moduleName, ".", "/") ~ ".d");
108   mkdirRecurse(dirName(fileName));
109   write(fileName, buffer[]);
110 }