<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Vita.Common;
using Vita.Entities;
using Vita.Entities.Web;
using Vita.Modules.WebClient;
using Vita.Modules.EncryptedData;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Vita.Modules.OAuthClient {

  public partial class OAuthClientModule : IOAuthClientService {

    // Standard response to get-access-token endpoint
    public class AccessTokenResponse {
      [Node("access_token")]
      public string AccessToken;
      [Node("expires_in")]
      public long ExpiresIn;
      [Node("token_type")]
      public string TokenType;
      [Node("refresh_token")]
      public string RefreshToken;
      [Node("id_token")]
      public string IdToken; //Open ID connect only
    }

    /// <summary>The query (parameters) portion of authorization URL - a page on OAuth server 
    /// that user is shown to approve the access by the client app. </summary>
    public const string AuthorizationUrlQuery = "?response_type=code&client_id={0}&redirect_uri={1}&scope={2}&state={3}";
    /// <summary>The query (parameters) portion; sent either as part of URL or as form-url-encoded body.</summary>
    public const string AccessTokenUrlQueryTemplate =
      "code={0}&redirect_uri={1}&grant_type=authorization_code&access_type=offline";
    public const string AccessTokenUrlQueryTemplateWithClientInfo =
      "code={0}&redirect_uri={1}&grant_type=authorization_code&access_type=offline&client_id={2}&client_secret={3}";
    public const string RefreshTokenUrlQueryTemplate =
      "refresh_token={0}&grant_type=refresh_token";
    public const string RefreshTokenUrlQueryTemplateWithClientInfo =
      "refresh_token={0}&grant_type=refresh_token&client_id={1}&client_secret={2}";
    public const string OpenIdClaimsParameter = "claims={0}";

    #region IAuthClientService
    public event AsyncEvent<OAuthEventArgs> Redirected;
    public event EventHandler<OAuthEventArgs> Authorized;


    public IOAuthClientFlow BeginOAuthFlow(IEntitySession session, Guid userId, string serverName, string scopes = null) {
      using(session.WithElevateRead()) {
        var acct = session.GetOAuthAccount(serverName);
        session.Context.ThrowIfNull(acct, ClientFaultCodes.ObjectNotFound, "serverName", "Account not registered for server {0}.", serverName);
        scopes = string.IsNullOrWhiteSpace(scopes) ? acct.Server.Scopes : scopes; //take all scopes
        var flow = acct.BeginOAuthFlow(userId, scopes);
        return flow;
      }

    }

    public IOAuthClientFlow GetOAuthFlow(IEntitySession session, Guid flowId) {
      var flow = session.GetEntity<IOAuthClientFlow>(flowId);
      if(flow == null)
        return null;
      if (flow.UserId != null) {
        var currUserId = session.Context.User.UserId;
        if(currUserId != flow.UserId.Value)
          return null; 
      }
      CheckExpired(flow);
      return flow; 
    }

    public async Task OnRedirected(OperationContext context, string state, string authCode, string error) {
      var session = context.OpenSystemSession();
      Guid reqId;
      Util.Check(Guid.TryParse(state, out reqId), "Invalid state parameter ({0}), expected GUID.", state);
      var flow = session.GetEntity<IOAuthClientFlow>(reqId);
      Util.Check(flow != null, "OAuth Redirect: invalid state parameter, OAuth flow not found.", state);
      ThrowIfExpired(flow); 
      flow.AuthorizationCode = authCode;
      flow.Error = error;
      flow.Status = string.IsNullOrWhiteSpace(error) ? OAuthFlowStatus.Authorized : OAuthFlowStatus.Error;
      session.SaveChanges();
      if(Redirected != null) {
        var args = new OAuthEventArgs(context, flow.Id);<commit_msg>minor fix in OAuth - return null if clientFlow does not belong to current user<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Vita.Common;
using Vita.Entities;
using Vita.Entities.Web;
using Vita.Modules.WebClient;
using Vita.Modules.EncryptedData;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Vita.Modules.OAuthClient {

  public partial class OAuthClientModule : IOAuthClientService {

    // Standard response to get-access-token endpoint
    public class AccessTokenResponse {
      [Node("access_token")]
      public string AccessToken;
      [Node("expires_in")]
      public long ExpiresIn;
      [Node("token_type")]
      public string TokenType;
      [Node("refresh_token")]
      public string RefreshToken;
      [Node("id_token")]
      public string IdToken; //Open ID connect only
    }

    /// <summary>The query (parameters) portion of authorization URL - a page on OAuth server 
    /// that user is shown to approve the access by the client app. </summary>
    public const string AuthorizationUrlQuery = "?response_type=code&client_id={0}&redirect_uri={1}&scope={2}&state={3}";
    /// <summary>The query (parameters) portion; sent either as part of URL or as form-url-encoded body.</summary>
    public const string AccessTokenUrlQueryTemplate =
      "code={0}&redirect_uri={1}&grant_type=authorization_code&access_type=offline";
    public const string AccessTokenUrlQueryTemplateWithClientInfo =
      "code={0}&redirect_uri={1}&grant_type=authorization_code&access_type=offline&client_id={2}&client_secret={3}";
    public const string RefreshTokenUrlQueryTemplate =
      "refresh_token={0}&grant_type=refresh_token";
    public const string RefreshTokenUrlQueryTemplateWithClientInfo =
      "refresh_token={0}&grant_type=refresh_token&client_id={1}&client_secret={2}";
    public const string OpenIdClaimsParameter = "claims={0}";

    #region IAuthClientService
    public event AsyncEvent<OAuthEventArgs> Redirected;
    public event EventHandler<OAuthEventArgs> Authorized;


    public IOAuthClientFlow BeginOAuthFlow(IEntitySession session, Guid userId, string serverName, string scopes = null) {
      using(session.WithElevateRead()) {
        var acct = session.GetOAuthAccount(serverName);
        session.Context.ThrowIfNull(acct, ClientFaultCodes.ObjectNotFound, "serverName", "Account not registered for server {0}.", serverName);
        scopes = string.IsNullOrWhiteSpace(scopes) ? acct.Server.Scopes : scopes; //take all scopes
        var flow = acct.BeginOAuthFlow(userId, scopes);
        return flow;
      }

    }

    public IOAuthClientFlow GetOAuthFlow(IEntitySession session, Guid flowId) {
      // Limit to logged in user, to avoid authorizatio exception
      var query = session.EntitySet<IOAuthClientFlow>().Where(f => f.Id == flowId);
      var user = session.Context.User;
      if(user.Kind == UserKind.AuthenticatedUser)
        query = query.Where(f => f.UserId == user.UserId);
      var flow = query.FirstOrDefault(); 
      if(flow == null)
        return null;
      if (flow.UserId != null) {
        var currUserId = session.Context.User.UserId;
        if(currUserId != flow.UserId.Value)
          return null; 
      }
      CheckExpired(flow);
      return flow; 
    }

    public async Task OnRedirected(OperationContext context, string state, string authCode, string error) {
      var session = context.OpenSystemSession();
      Guid reqId;
      Util.Check(Guid.TryParse(state, out reqId), "Invalid state parameter ({0}), expected GUID.", state);
      var flow = session.GetEntity<IOAuthClientFlow>(reqId);
      Util.Check(flow != null, "OAuth Redirect: invalid state parameter, OAuth flow not found.", state);
      ThrowIfExpired(flow); 
      flow.AuthorizationCode = authCode;
      flow.Error = error;
      flow.Status = string.IsNullOrWhiteSpace(error) ? OAuthFlowStatus.Authorized : OAuthFlowStatus.Error;
      session.SaveChanges();
      if(Redirected != null) {
        var args = new OAuthEventArgs(context, flow.Id);
{'fraction_non_alphanumeric': 0.10373181530676787, 'fraction_numerical': 0.004301075268817204, 'mean_word_length': 3.877236273904997, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5106325', 'n_tokens_mistral': 2260, 'n_tokens_neox': 2248, 'n_words': 563}