cyclosproject / ng-openapi-gen

An OpenAPI 3.0 codegen for Angular
MIT License
397 stars 132 forks source link

feat: parse security #20

Closed RGunning closed 5 years ago

RGunning commented 5 years ago

Problem

Currently, security information in the openApi file is not being parsed by the generator. Instead the documentation just recommends using HTTP interceptors.

Solution

Added basic parsing of the security schema into operations object for use in templates. I didn't add the security to the template as this will require more thought on how best to handle and may be best left to custom templates.

luisfpg commented 5 years ago

Thanks for the PR. This is something I've been thinking of for some time, and it's a start. My idea is to add to the generated ApiConfiguration a method per security definition. Suppose there's a security scheme called login, of type basic http auth, and another called apiKey, which sets a header. Ideally, the generated ApiConfiguration would be something like:

/**
 * Global configuration
 */
@Injectable({
  providedIn: 'root',
})
export class ApiConfiguration {
  rootUrl: string = 'http://www.example.com';
  security: {
    login: BasicSecurityScheme,
    apiKey: ApiKeySecurityScheme
  }
  constructor() {
    this.security = {
      login: new BasicSecurityScheme(),
      apiKey: new ApiKeySecurityScheme('HeaderName')
    }
  }
}

export interface SecurityScheme {
  apply: (req: HttpRequest<any>) => HttpRequest<any>;
  clear(): void;
}

export class BasicSecurityScheme implements SecurityScheme {
  private username?: string;
  private password?: string;

  set(username: string, password: string): void {
    this.username = username;
    this.password = password;
  }

  clear() {
    this.username = undefined;
    this.password = undefined;
  }

  apply(req: HttpRequest<any>): HttpRequest<any> {
    if (this.username && this.password) {
      const headers = new HttpHeaders();
      headers.append('Authorization', 'Basic ' + btoa(this.username + ':' + this.password));
      return req.clone({
        headers: headers
      });
    } else {
      return req;
    }
  }
}

export class ApiKeySecurityScheme implements SecurityScheme {
  private value?: string;

  constructor(public header: string) {
  }

  set(value: string): void {
    this.value = value;
  }

  clear() {
    this.value = undefined;
  }

  apply(req: HttpRequest<any>): HttpRequest<any> {
    if (this.value) {
      const headers = new HttpHeaders();
      headers.append(this.header, this.value);
      return req.clone({
        headers: headers
      });
    } else {
      return req;
    }
  }
}

This way, for example, once the user logs in, the component would inject the ApiConfiguration and just call:

this.apiConfiguration.security.login.set(usr, pwd);
RGunning commented 5 years ago

The setup with the api configuration looks good and is pretty much how I was planning to set it up in my custom template I'm working on.

I'll keep the scope of this PR limited to the parsing, but would happily help with your suggested setup above.

I've noticed a couple of bugs in this PR that need fixed. As the parsing I did doesn't fully implement the openApi specs currently. Looking at the schema I wasn't implementing the root security, the logical and/or, or the scoping fully.