/* * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET). * Copyright (c) 2021 Socialvoid.NET Authors. * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this source code of library. * If not, see . */ using System; using System.Text; using System.Net.Http; using System.IO; using Socialvoid.Security; using Socialvoid.Security.Otp; using Socialvoid.JObjects; using Socialvoid.Errors.ServerErrors; using Socialvoid.Errors.AuthenticationErrors; using Socialvoid.Errors.ValidationErrors; using MType = System.Net.Http.Headers.MediaTypeHeaderValue; namespace Socialvoid.Client { /// /// Socialvoid client. /// since: v0.0.0 /// public abstract class SocialvoidClient { //------------------------------------------------- #region Constant's Region /// /// the content type of all of our http requests. /// since: v0.0.0 /// protected internal const string ContentType = "application/json-rpc"; /// /// the username key in jsonrpc request params. /// since: v0.0.0 /// protected const string UsernameKey = "username"; /// /// the password key in jsonrpc request params. /// since: v0.0.0 /// protected const string PasswordKey = "password"; /// /// the otp key in jsonrpc request params. /// since: v0.0.0 /// protected const string OtpKey = "otp"; /// /// the public hash key in jsonrpc request params. /// since: v0.0.0 /// protected const string PublicHashKey = "public_hash"; /// /// the private hash key in jsonrpc request params. /// since: v0.0.0 /// protected const string PrivateHashKey = "private_hash"; /// /// the platform key in jsonrpc request params. /// since: v0.0.0 /// protected const string PlatformKey = "platform"; /// /// the name key in jsonrpc request params. /// since: v0.0.0 /// protected const string NameKey = "name"; /// /// the version key in jsonrpc request params. /// since: v0.0.0 /// protected const string VersionKey = "version"; /// /// authenticate_user method value. /// since: v0.0.0 /// protected const string AuthenticateUserMethod = "session.authenticate_user"; /// /// authenticate_user method value. /// since: v0.0.0 /// protected const string CreateSessionMethod = "session.create"; /// /// authenticate_user method value. /// since: v0.0.0 /// protected const string SessionIDKey = "session_identification"; #endregion //------------------------------------------------- #region static Properties Region // some members here #endregion //------------------------------------------------- #region Properties Region /// /// The public hash of the client. /// since: v0.0.0 /// public string PublicHash { get; protected set; } /// /// The private hash of the client. /// since: v0.0.0 /// public string PrivateHash { get; protected set; } /// /// The platform of the client. /// since: v0.0.0 /// public string Platform { get; protected set; } /// /// The name of the client. /// since: v0.0.0 /// public string ClientName { get; protected set; } /// /// The version of the client. /// since: v0.0.0 /// public string Version { get; protected set; } /// /// /// since: v0.0.0 /// public HttpClient HttpClient { get; protected set; } #endregion //------------------------------------------------- #region static field's Region // some members here #endregion //------------------------------------------------- #region field's Region /// /// the endpoint url of socialvoid servers. /// since: v0.0.0 /// protected internal string _endpoint = "http://socialvoid.qlg1.com:5601/"; /// /// Session object of this client. /// since: v0.0.0 /// protected internal SessionEstablished _session; /// /// if the client should send an otp answer in the next request, /// this field should be set to true. /// since: v0.0.0 /// protected bool _should_otp; /// /// the last otp value of the client which should be sent in the /// next jsonrpc request. /// since: v0.0.0 /// protected string _otp; /// /// since: v0.0.0 /// protected readonly MType _contentTypeValue = MType.Parse(ContentType); #endregion //------------------------------------------------- #region static event field's Region // some members here #endregion //------------------------------------------------- #region event field's Region // some members here #endregion //------------------------------------------------- #region Constructor's Region /// /// Creates a new instance of the class. /// since: v0.0.0 /// /// /// the public hash of the client. /// /// /// the private hash of the client. /// /// /// the platform of the client. /// /// /// the client name. /// /// /// the version of the client. /// protected SocialvoidClient(string publicHash, string privateHash, string platform, string name, string version) { PublicHash = publicHash; PrivateHash = privateHash; Platform = platform; ClientName = name; Version = version; HttpClient = new(); } #endregion //------------------------------------------------- #region Destructor's Region // some members here #endregion //------------------------------------------------- #region Initialize Method's Region // some methods here #endregion //------------------------------------------------- #region Graphical Method's Region // some methods here #endregion //------------------------------------------------- #region event Method's Region // some methods here #endregion //------------------------------------------------- #region overrided Method's Region // some methods here #endregion //------------------------------------------------- #region ordinary Method's Region /// /// CreateSession method (session.create), establishes a new session /// to the network. /// Please do notice that new and unauthenticated sessions /// expires after 10 minutes of inactivity, authenticating to the session /// will increase the expiration time to 72 hours of inactivity. This timer /// is reset whenever the session is used in one way or another. /// since: v0.0.0 /// /// /// Thrown if the server encounters an internal error. /// /// /// Thrown if the parameter passed as client name is not valid. /// /// /// Thrown if the parameter passed as public hash is not valid. /// /// /// Thrown if the parameter passed as private hash is not valid. /// /// /// Thrown if the parameter passed as platform is not valid. /// /// /// Thrown if the parameter passed as version is not valid. /// public virtual SessionEstablished CreateSession() { JArgs args = new(){ {PublicHashKey, PublicHash}, {PrivateHashKey, PrivateHash}, {PlatformKey, Platform}, {NameKey, ClientName}, {VersionKey, Version}, }; var request = GetRpcRequest(CreateSessionMethod, args); var message = new HttpRequestMessage(HttpMethod.Post, _endpoint); message.Content = SerializeContent(request); message.Content.Headers.ContentType = _contentTypeValue; var jresp = ParseContent(message); if (!string.IsNullOrEmpty(jresp.Result.ChallengeSecret)) { _should_otp = true; _otp = GetChallengeAnswer(jresp.Result.ChallengeSecret); // set challenege secret to null to avoid sending it again. // this will avoid future conflicts in using old challenge secret. jresp.Result.ChallengeSecret = null; } _session = jresp.Result; return _session; } /// /// AuthenticateUser method (session.authenticate_user), /// Authenticates a user via a Username and Password combination. /// Optionally two-factor authentication if the account has enabled it. /// Once authenticated, the session will be able to access methods that /// requires authentication and preform operations as the authenticated /// user. /// since: v0.0.0 /// /// /// The Session Identification object. /// /// /// The username of the user to authenticate to. /// /// /// The password used to authenticate to this account. /// /// /// The optional one-time password used to authenticate to this account. /// It will be ignored by server if empty or larger than 64 characters. /// /// /// Thrown if the server encounters an internal error. /// /// /// Thrown if the authentication fails. /// /// /// Thrown if the user is already authenticated. /// /// /// Thrown if the user is not able to authenticate. /// /// /// Thrown if the session challenge answer is invalid. /// /// /// Thrown if the username or password is incorrect. /// /// /// Thrown if the two-factor authentication code is incorrect. /// /// /// Thrown if the user is not authenticated and the private access token is required. /// /// /// Thrown if the session has expired. /// /// /// Thrown if two-factor authentication is required. /// public virtual void AuthenticateUser(string username, string password, string otp = null, SessionIdentification sessionID = null) { if (sessionID == null && _session != null) { sessionID = new() { SessionID = _session.SessionID, ClientPublicHash = PublicHash }; } JArgs args = new(){ {UsernameKey, username}, {PasswordKey, password}, {SessionIDKey, sessionID}, }; // check if the passed-by otp argument is valid or not. // if yes, ignore _otp field and use user's specified otp value. // otherwise check for _should_otp and see if we should send an // otp answer or not. if (IsOtpValid(otp)) { args.Add(OtpKey, otp); sessionID.ChallengeAnswer = otp; } else if (_should_otp && IsOtpValid(_otp)) { // after adding otp answer to args, don't forget to set // _should_otp to false (and _otp to null). args.Add(OtpKey, _otp); sessionID.ChallengeAnswer = _otp; _should_otp = false; _otp = null; } var request = GetRpcRequest(AuthenticateUserMethod, args); var message = new HttpRequestMessage(HttpMethod.Post, _endpoint); message.Content = SerializeContent(request); message.Content.Headers.ContentType = _contentTypeValue; var resp = HttpClient.Send(message); var contentStr = ReadFromContent(resp.Content); Console.WriteLine(contentStr); } /// /// returns a challenge's answer using the session's challenge secret. /// since: v0.0.0 /// protected internal virtual string GetChallengeAnswer(string secret) { var otp = new Totp(Encoding.UTF8.GetBytes(secret)); return KeyGeneration.GetSha1(otp.ComputeTotp() + PrivateHash);; //return null; } #endregion //------------------------------------------------- #region Get Method's Region /// /// Gets the session of this if and only /// if you have already established a session (or has loaded it from a file); /// oterwise it will just return a null object instead of throwing an exception. /// since: v0.0.0 /// public virtual SessionEstablished GetSession() { return _session; } /// /// since: v0.0.0 /// /// /// the method. /// /// /// the arguments. /// /// /// the request ID. /// /// /// set it to `true` if you want this request to have requestID parameter /// set. (if the passed-by id paramater is null, this method will generate /// a new id itself.) /// protected internal JRequest GetRpcRequest(string method, JArgs args = null, Nullable id = null, bool useID = true) { if (string.IsNullOrWhiteSpace(method)) { throw new ArgumentException( "method name in a rpc request cannot be null or empty", nameof(method)); } if (useID && (id == null)) { id = DateTime.Now.Ticks; } return useID && id != null && id.HasValue ? new() { Method = method, Arguments = args, ID = id.Value, } : new() { Method = method, Arguments = args, }; } /// /// since: v0.0.0 /// protected StringContent SerializeContent(JRequest request) { return new(request.Serialize()); } /// /// Parses the content of a as a /// . /// /// /// protected internal JResponse ParseContent( HttpRequestMessage message, bool ex = true) where VType : class { if (HttpClient == null) { throw new InvalidOperationException("HttpClient wasn't initialized"); } var resp = HttpClient.Send(message); if (resp == null) { throw new InvalidOperationException("HttpClient.Send returned null"); } return ParseContent(resp.Content, ex); } #endregion //------------------------------------------------- #region Set Method's Region // some methods here #endregion //------------------------------------------------- #region static Method's Region /// /// Creates a new instance of the class. /// since: v0.0.0 /// /// /// the public hash of the client. /// /// /// the private hash of the client. /// /// /// the platform of the client. /// /// /// the client name. /// /// /// the version of the client. /// public static SocialvoidClient GetClient(string publicHash, string privateHash, string platform, string name, string version) { if (string.IsNullOrWhiteSpace(publicHash) || publicHash.Length != 64) { throw new ArgumentException( "publicHash parameter is invalid", nameof(publicHash)); } if (string.IsNullOrWhiteSpace(privateHash) || privateHash.Length != 64) { throw new ArgumentException( "privateHash parameter is invalid", nameof(privateHash)); } if (string.IsNullOrWhiteSpace(platform)) { throw new ArgumentException( "platform cannot be null or empty", nameof(platform)); } if (string.IsNullOrWhiteSpace(version)) { throw new ArgumentException( "version cannot be null or empty", nameof(version)); } return new SvClient(publicHash, privateHash, platform, name, version); } /// /// Checks if a string can be sent as a TOTOP answer or not. /// since: v0.0.0 /// /// /// the otp string to check. /// /// /// true if the otp string is valid; otherwise false. /// protected internal static bool IsOtpValid(string otp) { return !(string.IsNullOrWhiteSpace(otp) || otp.Length > 64); } /// /// reads the content of a as a string. /// returns null if the stream cannot be read. /// /// /// protected internal static string ReadFromContent(HttpContent content) { var stream = content.ReadAsStream(); if (stream == null || !stream.CanRead) { return null; } return new StreamReader(stream).ReadToEnd(); } /// /// Parses the content of a as a /// . /// /// /// protected internal static JResponse ParseContent( HttpContent content, bool ex = true) where VType : class { var jresp = JResponse.Deserialize(ReadFromContent(content)); if (ex && jresp.HasError()) { throw jresp.GetException(); } return jresp; } #endregion //------------------------------------------------- } }