diff --git a/.gitignore b/.gitignore index 3e8a1553fcff7845c779d3cb8014e151a9931506..26bcd2533c237cc5afccd5984ad45900a65e1ab8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +packages + # User-specific files *.rsuser *.suo diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 7fc06df1df52584a329ed3e08c28bfd1c6101ba3..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# signit-sdk-net - -#### Description -signit.cn 的 .NET 接口 - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) \ No newline at end of file diff --git a/README.md b/README.md index 5996a68e6c59ffd72ef27037759eb60fab9a9580..b79fe8f8f5e4e13fc543ee15597477d569f6f486 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,15 @@ -# signit-sdk-net +# 欢迎使用易企签 .NET SDK -#### 介绍 -signit.cn 的 .NET 接口 +易企签 .NET SDK 包括: -#### 软件架构 -软件架构说明 +- token授权获取 +- 快捷签署 +- 提交企业实名认证(即:开通在线电子签约服务接口(企业)) +- 提交个人实名认证(即:开通在线电子签约服务接口(个人)) +- 启动信封签署流程 +了解更多易企签详情请访问 [https://www.signit.cn](https://www.signit.cn) -#### 安装教程 +## 更新日志 -1. xxxx -2. xxxx -3. xxxx - -#### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 码云特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目 -5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) \ No newline at end of file +### v1.0 ... diff --git a/Sdk.sln b/Sdk.sln new file mode 100644 index 0000000000000000000000000000000000000000..0175a056b0073a903413ac8cab9e9b62fb09d8d2 --- /dev/null +++ b/Sdk.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28917.181 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdk", "Sdk\Sdk.csproj", "{E583EAF3-04F9-4AB6-B265-D737568AAEFC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E583EAF3-04F9-4AB6-B265-D737568AAEFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E583EAF3-04F9-4AB6-B265-D737568AAEFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E583EAF3-04F9-4AB6-B265-D737568AAEFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E583EAF3-04F9-4AB6-B265-D737568AAEFC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {817A68CA-8343-42F8-990B-2BAB20226507} + EndGlobalSection +EndGlobal diff --git a/Sdk/Entity/BaseFileData.cs b/Sdk/Entity/BaseFileData.cs new file mode 100644 index 0000000000000000000000000000000000000000..47f9b24a84e37d74f9b616d80b661e7b2c7c4855 --- /dev/null +++ b/Sdk/Entity/BaseFileData.cs @@ -0,0 +1,8 @@ +namespace Signit.Sdk.Entity +{ + public class BaseFileData + { + public string Url { get; set; } + public string Base64 { get; set; } + } +} diff --git a/Sdk/Entity/CertData.cs b/Sdk/Entity/CertData.cs new file mode 100644 index 0000000000000000000000000000000000000000..44c70c4f9bab9e4734894e24a6253635d4dc0710 --- /dev/null +++ b/Sdk/Entity/CertData.cs @@ -0,0 +1,13 @@ +namespace Signit.Sdk.Entity +{ + /** + * 证书数据. 支持base64、URL、证书数据全局唯一ID中对的任意一种方式. + */ + public class CertData : BaseFileData + { + /** + * 证书数据的全局唯一ID. + */ + public string Wsid { get; set; } + } +} diff --git a/Sdk/Entity/Contact.cs b/Sdk/Entity/Contact.cs new file mode 100644 index 0000000000000000000000000000000000000000..3009d44de2270c04d41ea6a40a82394118beedee --- /dev/null +++ b/Sdk/Entity/Contact.cs @@ -0,0 +1,18 @@ +namespace Signit.Sdk.Entity +{ + /** + * 用户联系方式. 包含邮箱和手机号码. + */ + public class Contact + { + /** + * 电子邮箱. + */ + public string Email { get; set; } + + /** + * 手机号码. + */ + public string Phone { get; set; } + } +} \ No newline at end of file diff --git a/Sdk/Entity/FileData.cs b/Sdk/Entity/FileData.cs new file mode 100644 index 0000000000000000000000000000000000000000..ea2c98a8b62f2eb3dec104f54dea7e79ab02b37b --- /dev/null +++ b/Sdk/Entity/FileData.cs @@ -0,0 +1,16 @@ +namespace Signit.Sdk.Entity +{ + public class FileData + { + public string Url { get; set; } + public string Base64 { get; set; } + public bool EnableSinglePage { get; set; } + + public FileData() { } + + public FileData(string url) + { + Url = url; + } + } +} diff --git a/Sdk/Entity/IdCardImage.cs b/Sdk/Entity/IdCardImage.cs new file mode 100644 index 0000000000000000000000000000000000000000..ccb817b02020724a4923ef441a18f817157665b9 --- /dev/null +++ b/Sdk/Entity/IdCardImage.cs @@ -0,0 +1,23 @@ +namespace Signit.Sdk.Entity +{ + /// + /// 证件图像数据. + /// + public class IdCardImage + { + /// + /// 图像名称. + /// + public string ImageName { get; set; } + + /// + /// 图像标识码. + /// + public string ImageCode { get; set; } + + /// + /// 图像标识码. + /// + public IdCardImageData ImageData { get; set; } + } +} \ No newline at end of file diff --git a/Sdk/Entity/IdCardImageData.cs b/Sdk/Entity/IdCardImageData.cs new file mode 100644 index 0000000000000000000000000000000000000000..359db9397d313190eac72545782287e144c18b54 --- /dev/null +++ b/Sdk/Entity/IdCardImageData.cs @@ -0,0 +1,18 @@ +namespace Signit.Sdk.Entity +{ + /** + * 证件图片数据. + * + * @since 2.0.0 + */ + public class IdCardImageData : BaseFileData + { + /** + * 证件的文件全局唯一ID. + * + * @since 2.0.0 + */ + public string FileWsid { get; set; } + + } +} \ No newline at end of file diff --git a/Sdk/Entity/InitialValue.cs b/Sdk/Entity/InitialValue.cs new file mode 100644 index 0000000000000000000000000000000000000000..e8bf1cc69ab33941f38508dd7728137b892b35d4 --- /dev/null +++ b/Sdk/Entity/InitialValue.cs @@ -0,0 +1,73 @@ +using Signit.Sdk.Types; + +namespace Signit.Sdk.Entity +{ + /** + * 签名初始化数据。包含印章数据、手写数据、证书数据、证书签名证书秘钥访问口令、证书类型、 渲染模式、签名地理位置、联系方式、签名是否锁定、签名原因。 + * 印章数据、手写数据仅一个不为空的有效。 + */ + public class InitialValue + { + /** + * 印章数据. + */ + public SealData SealData { get; set; } + + /** + * 手写签名数据. + */ + public WriteData WriteData { get; set; } + + /** + * 证书数据. + */ + public CertData CertData { get; set; } + + /** + * 证书签名证书密钥访问口令. + */ + public string CertPassin { get; set; } + + /** + * 证书类型. + * 对应枚举:CertType + */ + public CertType CertType { get; set; } + + /** + * 渲染模式. 对应枚举: + * 对应枚举:RenderMode + */ + public RenderMode RenderingMode { get; set; } = RenderMode.GRAPHIC; + + /** + * 签名地理位置. + */ + public string Location { get; set; } + + /** + * 联系方式. + */ + public string Contact { get; set; } + + /** + * 签名是否锁定. + */ + public bool Locked { get; set; } + + /** + * 签名原因. + */ + public string Reason { get; set; } + + /** + * 文本框文本内容 + */ + public string TextContent { get; set; } + + /** + * 二维码内容字符串. + */ + public string QrcodeContent { get; set; } + } +} \ No newline at end of file diff --git a/Sdk/Entity/KeywordPosition.cs b/Sdk/Entity/KeywordPosition.cs new file mode 100644 index 0000000000000000000000000000000000000000..d928c7af7e81dc5c9445d9b6d69a80af0df94c97 --- /dev/null +++ b/Sdk/Entity/KeywordPosition.cs @@ -0,0 +1,30 @@ +using Signit.Sdk.Types; + +namespace Signit.Sdk.Entity +{ + public class KeywordPosition + { + public float? Width { get; set; } + public float? RelativeWidthRatio { get; set; } + public float? Height { get; set; } + public float? RelativeHeightRatio { get; set; } + public Direction Direction { get; set; } + public float? Offset { get; set; } + public float? RelativeOffsetRatio { get; set; } + public string Keyword { get; set; } + public float? Scale { get; set; } + public string Pages { get; set; } + + /// + /// x 方向的便移量 正是往右,负是往左,坐标缩放前进行最后的微调参数 + /// + public float? XOffset { get; set; } + + /// + /// y 方向的便移量 正是往上,负是往下,坐标缩放前进行最后的微调参数 + /// + public float? YOffset { get; set; } + + public int Index { get; set; } + } +} diff --git a/Sdk/Entity/PresetForm.cs b/Sdk/Entity/PresetForm.cs new file mode 100644 index 0000000000000000000000000000000000000000..5441ce078b33c0c2116c58d6fcc2ad1c94bbe11e --- /dev/null +++ b/Sdk/Entity/PresetForm.cs @@ -0,0 +1,113 @@ +using Signit.Sdk.Types; + +namespace Signit.Sdk.Entity +{ + /** + * 预设表单信息。 包括表单类型、印章/手写签名名字、所签署文件ID、签署表单坐标位置、表单是否必填、表单缩放比例 + * 、签名初始化值、表单是否允许修正、当前表单的自定义标识、是否是纠正创建的表单 + */ + public class PresetForm + { + /** + * 表单类型。 对应枚举:FormType + */ + public FormType FormType { get; set; } + + /** + * 当前表单要设置到的文件上,引用contentInfo.files[].id值. + */ + public string FileId { get; set; } + + /** + * 签署表单坐标位置. + */ + public SignerPosition Position { get; set; } + + /** + * 表单缩放比例. + */ + public float Scale { get; set; } = 1.0f; + + /** + * 签名初始化值. + */ + public InitialValue InitialValue { get; set; } + + /** + * 表单是否允许修正. + */ + public bool Revisable { get; set; } + + /** + * 当前表单的自定义标识. + */ + public string TagId { get; set; } + + /** + * 是否是纠正创建的表单. + */ + public bool Corrected { get; set; } + + /** + * 跨页表单距离页面边缘边距,单位px + * @since 2.1.0 + */ + public float Margin { get; set; } + + /** + * 防伪标记中心按顺时针方向,沿页面边缘偏移的量 + * @since 2.1.0 + */ + public float Offset { get; set; } + + /** + * 签署完成后证书所在的页数,若为空字符串或null,则等价于为均为无证书骑缝章。支持格式形如:'first','last','all','odd','even','1','1-5','1,3,4,7,8',... + * @since 2.1.0 + */ + public string CertPages { get; set; } + + /** + * 单位图片所占像素(当且仅当每页宽度不足1px时有效). + * @since 2.1.0 + */ + public int Pixel { get; set; } + + /** + * 单页数是否加盖骑缝章 + * @since 2.1.0 + */ + public bool SinglePageMark { get; set; } + + /** + * 是否重设骑缝章尺寸 + * @since 2.1.0 + */ + public bool Resizable { get; set; } + + /** + * 重设骑缝章尺寸宽度.单位px.当resizablee时该值的设置才有效=tru + * @since 2.1.0 + */ + public float ResizeWidth { get; set; } + + /** + * 重设骑缝章尺寸高度.单位px.当resizablee时该值的设置才有效=tru + * @since 2.1.0 + */ + public float ResizeHeight { get; set; } + + + /** + * 二维码宽,单位px. + * @since 2.1.0 + */ + public float Width { get; set; } + + + /** + * 二维码高,单位px. + * @since 2.1.0 + */ + public float Height { get; set; } + } +} \ No newline at end of file diff --git a/Sdk/Entity/Receiver.cs b/Sdk/Entity/Receiver.cs new file mode 100644 index 0000000000000000000000000000000000000000..65fcbc520a0688a202bf3cae416d0279d01850f9 --- /dev/null +++ b/Sdk/Entity/Receiver.cs @@ -0,0 +1,85 @@ +using Signit.Sdk.Types; +using System.Collections.Generic; + +namespace Signit.Sdk.Entity +{ + /** + * 接收方信息。 包含接收方名字、接收方联系方式、安全等级、接收方类型、 接收方是否必须预分配表单域、设置接收方私人信息、接收方处理顺序、 + * 签署认证等级、接收方所在企业名称、信封自定义元数据信息、预设表单信息. + */ + public class Receiver + { + /** + * 接收方名字. + */ + public string Name { get; set; } + /** + * 接收方联系方式. + */ + public Contact Contact { get; set; } + /** + * 安全等级. + *

+ * 对应枚举:SecureLevel + * + */ + public SecureLevel SecureLevel { get; set; } + /** + * 接收方类型。 接收方类型:ReceiverType + * + */ + public ReceiverType Type { get; set; } + /** + * 接收方是否必须预分配表单域. + */ + public bool NeedForm { get; set; } + /** + * 设置接收方私人信息. + */ + public string AssignedMessage { get; set; } + /** + * 接收方处理顺序. + */ + public int AssignedSequence { get; set; } + + /** + * 接收方所在企业名称. + */ + public string EnterpriseName { get; set; } + /** + * 信封自定义元数据信息. + */ + public string Metadata { get; set; } + /** + * 预设表单信息. + */ + public IList PresetForms { get; set; } + + public EnvelopeRoleType RoleType { get; set; } + + /** + * 流程完成后删除当前参与者的信封。 默认:false + * + */ + public bool DeleteCompletedEnvelope { get; set; } + /** + * 参与者处理表单各种模式的枚举 + */ + public ParticipantHandleMode HandleMode { get; set; } + + public IList SelectedAuthTypes { get; set; } + + /** + * 启用嵌入模式,调用方系统中直接嵌入易企签WEB流程时设置为true,签署流程消息只会通过webhook事件消息方式通知,用户在易企签平台设置的短信/邮件等消息将自动屏蔽。非必填,默认值:false. + * @since 2.1.0 + */ + public bool EnableEmbeddedMode { get; set; } + + /** + * 签署接收方用户在调用方系统的唯一标识.
+ * enableEmbeddedMode为false时,非必填;当enableEmbeddedMode为true时,则必填。默认:null + * @since 2.1.0 + */ + public string ClientId { get; set; } + } +} diff --git a/Sdk/Entity/RectanglePosition.cs b/Sdk/Entity/RectanglePosition.cs new file mode 100644 index 0000000000000000000000000000000000000000..191b915319887c334a5dab60ffbdc4499be30b93 --- /dev/null +++ b/Sdk/Entity/RectanglePosition.cs @@ -0,0 +1,12 @@ +namespace Signit.Sdk.Entity +{ + public class RectanglePosition + { + public float? Lrx { get; set; } + public float? Lry { get; set; } + public float? Ulx { get; set; } + public float? Uly { get; set; } + public float? Scale { get; set; } + public int? Page { get; set; } + } +} diff --git a/Sdk/Entity/SealData.cs b/Sdk/Entity/SealData.cs new file mode 100644 index 0000000000000000000000000000000000000000..4a9cbe8b59e470b46052dfb9d147688b34cf3c5d --- /dev/null +++ b/Sdk/Entity/SealData.cs @@ -0,0 +1,7 @@ +namespace Signit.Sdk.Entity +{ + public class SealData : FileData + { + public string Name { get; set; } + } +} diff --git a/Sdk/Entity/Sender.cs b/Sdk/Entity/Sender.cs new file mode 100644 index 0000000000000000000000000000000000000000..7b85d6752f7cb62fe32fe0c94e57bd1dbf0ac3b0 --- /dev/null +++ b/Sdk/Entity/Sender.cs @@ -0,0 +1,37 @@ +namespace Signit.Sdk.Entity +{ + /** + * 发送方信息。 包含姓名和联系方式. + */ + public class Sender + { + /** + * 发送者名字. + */ + public string Name { get; set; } + + /** + * 发送者联系方式. + */ + + public Contact Contact { get; set; } + /** + * 流程完成后删除当前参与者的信封.
+ * 默认:false + * + */ + public bool DeleteCompletedEnvelope { get; set; } + + /** + * 启用嵌入模式,调用方系统中直接嵌入易企签WEB流程时设置为true,签署流程消息只会通过webhook事件消息方式通知,用户在易企签平台设置的短信/邮件等消息将自动屏蔽。非必填,默认值:false. + * @since 2.1.0 + */ + public bool EnableEmbeddedMode { get; set; } + + /** + * 签署发送方用户在调用方系统的唯一标识.
+ * enableEmbeddedMode为false时,非必填;当enableEmbeddedMode为true时,则必填。默认:null + */ + public string ClientId { get; set; } + } +} diff --git a/Sdk/Entity/Signer.cs b/Sdk/Entity/Signer.cs new file mode 100644 index 0000000000000000000000000000000000000000..108f6ea0199dd4b4278460b2e790bd66d608d39f --- /dev/null +++ b/Sdk/Entity/Signer.cs @@ -0,0 +1,10 @@ +namespace Signit.Sdk.Entity +{ + public class Signer + { + public int Sequence { get; set; } + public SignerPosition Position { get; set; } + public SignerData Data { get; set; } + public SignerInfo SignerInfo { get; set; } + } +} diff --git a/Sdk/Entity/SignerData.cs b/Sdk/Entity/SignerData.cs new file mode 100644 index 0000000000000000000000000000000000000000..b51473b32ee95525611bea56f68d0e0859970ae5 --- /dev/null +++ b/Sdk/Entity/SignerData.cs @@ -0,0 +1,8 @@ +namespace Signit.Sdk.Entity +{ + public class SignerData + { + public SealData SealData { get; set; } + public SealData WriteData { get; set; } + } +} diff --git a/Sdk/Entity/SignerInfo.cs b/Sdk/Entity/SignerInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..1a02239f30c98b784b5b30ca39b23434327f11e4 --- /dev/null +++ b/Sdk/Entity/SignerInfo.cs @@ -0,0 +1,12 @@ +namespace Signit.Sdk.Entity +{ + public class SignerInfo + { + public string Name { get; set; } + public string OrgnizationName { get; set; } + public string IdCardNo { get; set; } + public string Location { get; set; } + public string Contact { get; set; } + public string Reason { get; set; } + } +} diff --git a/Sdk/Entity/SignerPosition.cs b/Sdk/Entity/SignerPosition.cs new file mode 100644 index 0000000000000000000000000000000000000000..37b302f03501fa2bef544ddebf21c11da7c05b82 --- /dev/null +++ b/Sdk/Entity/SignerPosition.cs @@ -0,0 +1,79 @@ +namespace Signit.Sdk.Entity +{ + public class SignerPosition + { + public static Builder CreateBuilder() => new Builder(); + + public KeywordPosition KeywordPosition { get; set; } + public RectanglePosition RectanglePosition { get; set; } + public string FieldName { get; set; } + + /** + * 表单坐标位置对象建造器 + * + * @since 1.0.0 + */ + public class Builder + { + private KeywordPosition keywordPosition; + private RectanglePosition rectanglePosition; + private string fieldName; + + /** + * + * @param keywordPosition + * 利用关键字定位签名矩形框所在位置的数据对象;利用关键字进行定位 + * {@link KeywordPosition} + * @return 利用关键字定位签名矩形框所在位置的数据对象建造器{@link PositionBuilder} + * @since 1.0.0 + */ + public Builder WithKeywordPosition(KeywordPosition keywordPosition) + { + this.keywordPosition = keywordPosition; + return this; + } + + /** + * + * @param rectanglePosition + * 直接设定好的签名矩形框坐标 {@link RectanglePosition} + * @return 利用关键字定位签名矩形框所在位置的数据对象建造器{@link PositionBuilder} + * @since 1.0.0 + */ + public Builder WithRectanglePosition(RectanglePosition rectanglePosition) + { + this.rectanglePosition = rectanglePosition; + return this; + } + + /** + * + * @param fieldsName + * 签名域名称;签名文件格式需要是pdf,且签名文件中已经设置好签名域名称对应的签名域 + * @return 利用关键字定位签名矩形框所在位置的数据对象建造器{@link PositionBuilder} + * @since 1.0.0 + */ + public Builder WithFieldName(string fieldName) + { + this.fieldName = fieldName; + return this; + } + + /** + * + * @return 表单坐标位置对象 {@link Position} + * @since 1.0.0 + */ + public SignerPosition Build() + { + return new SignerPosition + { + KeywordPosition = keywordPosition, + RectanglePosition = rectanglePosition, + FieldName = fieldName, + }; + } + } + + } +} diff --git a/Sdk/Entity/WriteData.cs b/Sdk/Entity/WriteData.cs new file mode 100644 index 0000000000000000000000000000000000000000..660ec9d5cc78b120bb23855ad66d9cc1cd6f2933 --- /dev/null +++ b/Sdk/Entity/WriteData.cs @@ -0,0 +1,18 @@ +namespace Signit.Sdk.Entity +{ + /** + * 手写签名数据. 支持URL/Base64/签名全局唯一ID/签名名字,代表签名数据. + */ + public class WriteData : BaseFileData + { + /** + * 手写签名全局唯一ID. + */ + public string Wsid { get; set; } + + /** + * 手写签名名子. + */ + public string Name { get; set; } + } +} diff --git a/Sdk/Extentions/StreamExtention.cs b/Sdk/Extentions/StreamExtention.cs new file mode 100644 index 0000000000000000000000000000000000000000..015d6b691e9778b0aa06fbb992949f1f617ec9ae --- /dev/null +++ b/Sdk/Extentions/StreamExtention.cs @@ -0,0 +1,30 @@ +using System.IO; +using Viyi.Util.Linq; + +namespace Signit.Sdk.Extentions +{ + public static class StreamExtention + { + public static T Write(this T stream, byte[] data) + where T : Stream + { + stream.Write(data, 0, data.Length); + return stream; + } + + public static T Write(this T stream, byte[] data, byte[] suffix) + where T : Stream + { + stream.Write(data); + stream.Write(suffix); + return stream; + } + + public static T Write(this T stream, params byte[][] multiData) + where T : Stream + { + multiData.ForEach(data => stream.Write(data)); + return stream; + } + } +} diff --git a/Sdk/Extentions/TaskExtentions.cs b/Sdk/Extentions/TaskExtentions.cs new file mode 100644 index 0000000000000000000000000000000000000000..6a9e67c18015e713a047302ebb3bb1cd8cdcfcf5 --- /dev/null +++ b/Sdk/Extentions/TaskExtentions.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Signit.Sdk.Extentions +{ + public static class TaskExtentions + { + public static T WaitResult(this Task task) + { + task.Wait(); + return task.Result; + } + } +} diff --git a/Sdk/Http/AbstractSignitRequest.cs b/Sdk/Http/AbstractSignitRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..36ea6e890f56d7ca9b1f6c40ea45f96dd18b2abb --- /dev/null +++ b/Sdk/Http/AbstractSignitRequest.cs @@ -0,0 +1,22 @@ +using Signit.Sdk.Types; + +namespace Signit.Sdk.Http +{ + public abstract class AbstractSignitRequest + { + /// + /// 调用方自定义标识,易企签会原封不动返回. + /// + public string CustomTag { get; set; } + + /// + /// 调用方自定义要求易企签的WEB平台在流程结束后需要跳转的指定URL地址 + /// + public string ReturnUrl { get; set; } + + /// + /// 调用方接受的响应数据类型,支持:BASE64/URL + /// + public AcceptDataType AcceptDataType { get; set; } + } +} diff --git a/Sdk/Http/AbstractSignitResponse.cs b/Sdk/Http/AbstractSignitResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..2f4056debac1d6c9b99968ffd37ff4fb97ad081f --- /dev/null +++ b/Sdk/Http/AbstractSignitResponse.cs @@ -0,0 +1,31 @@ +namespace Signit.Sdk.Http +{ + public abstract class AbstractSignitResponse + { + public string Code { get; set; } + public string Message { get; set; } + + // 自定义标识 + public string CustomTag { get; set; } + + // 服务调用唯一标识 + public string InvokeNo { get; set; } + + public bool IsSuccess + { + get + { + if (Code == null || Code.Length < 4) + { + return false; + } + + var descCode = Code.Substring(Code.Length - 4); + return int.TryParse(descCode, out int code) + && code >= 0 && code < 100; + } + } + + public ErrorResponse Error { get; set; } + } +} diff --git a/Sdk/Http/ApiOptions.cs b/Sdk/Http/ApiOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..f8cb95812fe05087365262c04c39540274344bf8 --- /dev/null +++ b/Sdk/Http/ApiOptions.cs @@ -0,0 +1,16 @@ +namespace Signit.Sdk.Http +{ + public class ApiOptions + { + public static ApiOptions Default => new ApiOptions(); + + public string EnvUrl = "https://open.signit.cn"; + public string Encoding = "UTF-8"; + public string BaseApiUrl = "/v1/open/signatures/quick-sign"; + public string OAuthTokenUrl = "/v1/oauth/oauth/token"; + public string ClientId = "client_id"; + public string ClientSecret = "client_secret"; + public string GrantType = "grant_type"; + public string AppIdKey = "X-Signit-App-Id"; + } +} diff --git a/Sdk/Http/Authentication.cs b/Sdk/Http/Authentication.cs new file mode 100644 index 0000000000000000000000000000000000000000..7f9e568523feee81ff8552a22a26e0ce27a124a8 --- /dev/null +++ b/Sdk/Http/Authentication.cs @@ -0,0 +1,114 @@ +using Signit.Sdk.Types; +using Signit.Sdk.Util; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using Viyi.Util.Codec; + +namespace Signit.Sdk.Http +{ + public class Authentication + { + private const string KeyBase64 = "9ijhWI+0fNf0RSRJxgPt7q2zciyGVxmcco95I+gKids="; + + public string AppId { get; set; } = ""; + public string SecuretKey { get; set; } = ""; + public string AccessToken { get; set; } = ""; + public TokenType AccessTokenType { get; set; } = TokenType.CLIENT_CREDENTIALS; + + // TODO private string cacheTokenDir = System.getProperty("java.io.tmpdir"); + public string CacheTokenDir = Path.GetTempPath(); + + + public Authentication() { } + + public Authentication(string apiKey, string secretKey, TokenType authType) + { + AppId = apiKey; + SecuretKey = secretKey; + AccessTokenType = authType; + InitFromLocal(); + } + + public bool HasAppId => !string.IsNullOrEmpty(AppId); + public bool HasSecretKey => !string.IsNullOrEmpty(SecuretKey); + + public bool HasAccessToken + { + get + { + InitFromLocal(); + return !string.IsNullOrEmpty(AccessToken); + } + } + + private void CacheToLocal() + { + if (!string.IsNullOrWhiteSpace(AccessToken)) + { + return; + } + + string filePath = GenerateCachedTokenFilename(); + if (filePath == null) + { + return; + } + + try + { + byte[] data = Encoding.ASCII.GetBytes(AccessToken); + byte[] secretData = data.AesEncrypt(KeyBase64); + File.WriteAllBytes(filePath, secretData); + } + catch + { + // DO-NOTHING + return; + } + } + + private void InitFromLocal() + { + if (!string.IsNullOrWhiteSpace(AccessToken)) + { + return; + } + + string filePath = GenerateCachedTokenFilename(); + if (filePath == null || !File.Exists(filePath)) + { + return; + } + + try + { + var secretData = File.ReadAllBytes(filePath); + var data = secretData.AesDecrypt(KeyBase64); + AccessToken = Encoding.ASCII.GetString(data); + } + catch + { + // DO-NOTHING + return; + } + } + + private string GenerateCachedTokenFilename() + { + if (string.IsNullOrEmpty(AppId) || string.IsNullOrEmpty(SecuretKey)) + { + return null; + } + + var combine = string.Join(",", new[] + { + AppId, SecuretKey, AccessTokenType.ToString() + }); + + var hash = MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(combine)); + var filename = Path.Combine(CacheTokenDir, hash.Base64Encode()); + return filename; + } + } +} diff --git a/Sdk/Http/Enterprise/EnterpriseAgent.cs b/Sdk/Http/Enterprise/EnterpriseAgent.cs new file mode 100644 index 0000000000000000000000000000000000000000..18887bb2ca51c009a547456846cf68b8aa964e7e --- /dev/null +++ b/Sdk/Http/Enterprise/EnterpriseAgent.cs @@ -0,0 +1,42 @@ +using Signit.Sdk.Entity; +using Signit.Sdk.Types; +using System.Collections.Generic; + +namespace Signit.Sdk.Http.Enterprise +{ + /// + /// 经办人信息 + /// + public class EnterpriseAgent + { + /// + /// 委托书照片信息. + /// + public IdCardImage TrustInstrumentImage { get; set; } + + /// + /// 待认证的用户姓名. + /// + public string Name { get; set; } + + /// + /// 待认证的用户手机号. + /// + public string Phone { get; set; } + + /// + /// 待认证的用户证件类型. + /// + public IdCardType IdCardType { get; set; } + + /// + /// 待认证的用户证件号. + /// + public string IdCardNo { get; set; } + + /// + /// 待认证的用户证件照片数组. + /// + public IList IdCardImages { get; set; } + } +} \ No newline at end of file diff --git a/Sdk/Http/Enterprise/EnterpriseBankCardInfo.cs b/Sdk/Http/Enterprise/EnterpriseBankCardInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..1fe9d02b3ddf4d4a32ae6c137b67cb2477d3e539 --- /dev/null +++ b/Sdk/Http/Enterprise/EnterpriseBankCardInfo.cs @@ -0,0 +1,33 @@ +namespace Signit.Sdk.Http.Enterprise +{ + /// + /// 对公银行账户信息 + /// + public class EnterpriseBankCardInfo + { + /// + /// 银行(如:中国农业银行等). + /// + public string Bank { get; set; } + + /// + /// 银行支行. + /// + public string BankBranch { get; set; } + + /// + /// 银行卡号. + /// + public string BankCardNo { get; set; } + + /// + /// 地区(省,市,区/县). + /// + public string Area { get; set; } + + /// + /// 联行号. + /// + public string UnionBankNo { get; set; } + } +} diff --git a/Sdk/Http/Enterprise/EnterpriseLegalPerson.cs b/Sdk/Http/Enterprise/EnterpriseLegalPerson.cs new file mode 100644 index 0000000000000000000000000000000000000000..24a63386ea0983ffb3a449eb45a2b8c6feb4df21 --- /dev/null +++ b/Sdk/Http/Enterprise/EnterpriseLegalPerson.cs @@ -0,0 +1,37 @@ +using Signit.Sdk.Entity; +using Signit.Sdk.Types; +using System.Collections.Generic; + +namespace Signit.Sdk.Http.Enterprise +{ + /// + /// 法人信息 + /// + public class EnterpriseLegalPerson + { + /// + /// 待认证的用户姓名. + /// + public string Name { get; set; } + + /// + /// 待认证的用户手机号. + /// + public string Phone { get; set; } + + /// + /// 待认证的用户证件类型. + /// + public IdCardType IdCardType { get; set; } + + /// + /// 待认证的用户证件号. + /// + public string IdCardNo { get; set; } + + /// + /// 待认证的用户证件照片数组. + /// + public IList IdCardImages { get; set; } + } +} diff --git a/Sdk/Http/Enterprise/EnterpriseVerifyRequest.cs b/Sdk/Http/Enterprise/EnterpriseVerifyRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..0745b3e7e3be9a690f5a6244162b0566638fce8c --- /dev/null +++ b/Sdk/Http/Enterprise/EnterpriseVerifyRequest.cs @@ -0,0 +1,67 @@ +using Signit.Sdk.Entity; +using Signit.Sdk.Types; +using System.Collections.Generic; + +namespace Signit.Sdk.Http.Enterprise +{ + /// + /// 企业实名认证请求数据结构. + /// + public class EnterpriseVerifyRequest : AbstractSignitRequest + { + /// + /// 待认证企业名称. + /// + public string Name { get; set; } + + /// + /// 经办人信息. + /// + public EnterpriseAgent Agent { get; set; } + + /// + /// 法人信息. + /// + public EnterpriseLegalPerson LegalPerson { get; set; } + + /// + /// 统一社会信用代码. + /// + public string UnifiedSocialCode { get; set; } + + /// + /// 营业执照照片信息. + /// + public IdCardImage BusinessLicenceImage { get; set; } + + /// + /// 组织机构代码(已由“统一社会信用代码”替换). + /// + public string OrgCode { get; set; } + + /// + /// 组织机构代码证照片信息. + /// + public IdCardImage OrgImage { get; set; } + + /// + /// 工商注册号(已由“统一社会信用代码”替换). + /// + public string RegistCode { get; set; } + + /// + /// LEGAL_PERSON-法人认证,AGENT-经办人认证. + /// + public EnterpriseAuthType AuthType { get; set; } + + /// + /// 企业对公银行信息. + /// + public EnterpriseBankCardInfo BankCardInfo { get; set; } + + /// + /// 额外认证所需照片信息数组. + /// + public IList ExtraAuthImages { get; set; } + } +} \ No newline at end of file diff --git a/Sdk/Http/Enterprise/EnterpriseVerifyResponse.cs b/Sdk/Http/Enterprise/EnterpriseVerifyResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..ef2f99250e669a1816fc3de36679575f8429574f --- /dev/null +++ b/Sdk/Http/Enterprise/EnterpriseVerifyResponse.cs @@ -0,0 +1,6 @@ +namespace Signit.Sdk.Http.Enterprise +{ + public class EnterpriseVerifyResponse : AbstractSignitResponse + { + } +} diff --git a/Sdk/Http/Envelope/EnvelopeBasicInfo.cs b/Sdk/Http/Envelope/EnvelopeBasicInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..4ee4af034ba141bc9d83ed7ee0e89361f47f6497 --- /dev/null +++ b/Sdk/Http/Envelope/EnvelopeBasicInfo.cs @@ -0,0 +1,37 @@ +using Signit.Sdk.Types; + +namespace Signit.Sdk.Http.Envelope +{ + /** + * 信封基本信息. 包含信封标题、信封主题、信封类型、信封认证等级、自定义元数据信息. + */ + public class EnvelopeBasicInfo + { + /** + * 信封标题. + */ + public string Title { get; set; } + + /** + * 信封主题. + */ + public string Subject { get; set; } + + /** + * 信封类型. + * 对应枚举:EnvelopeType + */ + public EnvelopeType Type { get; set; } + + /** + * 信封认证等级. + * 对应枚举:AuthLevel + */ + public AuthLevel AuthLevel { get; set; } + + /** + * 自定义元数据信息. + */ + public string Metadata { get; set; } + } +} \ No newline at end of file diff --git a/Sdk/Http/Envelope/EnvelopeContentInfo.cs b/Sdk/Http/Envelope/EnvelopeContentInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..84da7d0d4d9e45ba057db99f369664abb9c5513f --- /dev/null +++ b/Sdk/Http/Envelope/EnvelopeContentInfo.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Signit.Sdk.Http.Envelope +{ + /** + * 签署文件内容 + */ + public class EnvelopeContentInfo + { + /** + * 签署文件详细信息列表. + */ + public IList Files { get; set; } + } +} \ No newline at end of file diff --git a/Sdk/Http/Envelope/EnvelopeFile.cs b/Sdk/Http/Envelope/EnvelopeFile.cs new file mode 100644 index 0000000000000000000000000000000000000000..5641bce40225d13684634816baf2930fae361a1d --- /dev/null +++ b/Sdk/Http/Envelope/EnvelopeFile.cs @@ -0,0 +1,55 @@ +using Signit.Sdk.Entity; +using Signit.Sdk.Types; + +namespace Signit.Sdk.Http.Envelope +{ + /// + /// 签署文件详细信息。包含:待签文件i唯一标识ID、该文件排列顺序、签署文件的数据、 + /// 文件名、文件拥有者访问口令、文件类型、文件是否作为附件、自定义元数据信息 + /// + public class EnvelopeFile + { + /// + /// 待签文件唯一标识ID. + /// + public string Id { get; set; } + + /// + /// 该文件排列顺序. + /// + public int Sequence { get; set; } + + /// + /// 签署文件的数据. + /// + public BaseFileData Data { get; set; } + + /// + /// 文件名. + /// + public string FileName { get; set; } + + /// + /// 文件拥有者访问口令. + /// + public string FileOverPassin { get; set; } + + /// + /// 文件类型. + /// 对应枚举:FileType + /// + public FileType ContentType { get; set; } + + /// + /// 文件是否作为附件. + /// 默认为false + /// + public bool IsAttached { get; set; } + + /// + /// 自定义元数据信息. + /// + public string Metadata { get; set; } + + } +} \ No newline at end of file diff --git a/Sdk/Http/Envelope/EnvelopeParticipantInfo.cs b/Sdk/Http/Envelope/EnvelopeParticipantInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..893cc32e18303f1496bcbc524a19aee7aeeec980 --- /dev/null +++ b/Sdk/Http/Envelope/EnvelopeParticipantInfo.cs @@ -0,0 +1,21 @@ +using Signit.Sdk.Entity; +using System.Collections.Generic; + +namespace Signit.Sdk.Http.Envelope +{ + /** + * 参与文件签署人员的信息. 包含发送者信息和接受者信息列表. + */ + public class EnvelopeParticipantInfo + { + /** + * 发送者信息. + */ + public Sender Sender { get; set; } + + /** + * 接受者信息列表. + */ + public IList Receivers { get; set; } + } +} diff --git a/Sdk/Http/Envelope/StartEnvelopeRequest.cs b/Sdk/Http/Envelope/StartEnvelopeRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..b7f8e9967c9e12a4f9e3c388f982f4f306a118c5 --- /dev/null +++ b/Sdk/Http/Envelope/StartEnvelopeRequest.cs @@ -0,0 +1,23 @@ +namespace Signit.Sdk.Http.Envelope +{ + /// + /// 信封启动服务请求体. 包含信封基本信息、信封流程中的签署文件内容、签署参与者信息、自定义跳转URL、自定义可接受数据类型、自定义标识. + /// + public class StartEnvelopeRequest : AbstractSignitRequest + { + /// + /// 信封基本信息. + /// + public EnvelopeBasicInfo Basicinfo { get; set; } + + /// + /// 信封流程中的签署文件内容. + /// + public EnvelopeContentInfo ContentInfo { get; set; } + + /// + /// 签署参与者信息. + /// + public EnvelopeParticipantInfo ParticipantInfo { get; set; } + } +} diff --git a/Sdk/Http/Envelope/StartEnvelopeResponse.cs b/Sdk/Http/Envelope/StartEnvelopeResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..d9c55a1f9e4d8039fc98fa043cf244a055e7eef2 --- /dev/null +++ b/Sdk/Http/Envelope/StartEnvelopeResponse.cs @@ -0,0 +1,11 @@ +namespace Signit.Sdk.Http.Envelope +{ + /// + /// 启动信封响应 + /// + public class StartEnvelopeResponse : AbstractSignitResponse + { + + } + +} diff --git a/Sdk/Http/ErrorResponse.cs b/Sdk/Http/ErrorResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..4aa9c7df05945c49437ade2197a3d1d5a69d35d8 --- /dev/null +++ b/Sdk/Http/ErrorResponse.cs @@ -0,0 +1,11 @@ +namespace Signit.Sdk.Http +{ + public class ErrorResponse + { + public string Code { get; set; } + public string ErrorWsid { get; set; } + public string UserMessage { get; set; } + public string DeveloperMessage { get; set; } + public string MoreInfo { get; set; } + } +} diff --git a/Sdk/Http/HttpClientSync.Response.cs b/Sdk/Http/HttpClientSync.Response.cs new file mode 100644 index 0000000000000000000000000000000000000000..546d403f914242dadc5578b38fc6fec565f26d34 --- /dev/null +++ b/Sdk/Http/HttpClientSync.Response.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using System.Net; + +namespace Signit.Sdk.Http +{ + partial class HttpClientSync + { + public class Response + { + public HttpStatusCode Code { get; set; } + public string Content { get; set; } + public bool IsSuccessStatusCode { get; internal set; } + + public T GetObject() + { + return string.IsNullOrWhiteSpace(Content) + ? default(T) + : JsonConvert.DeserializeObject(Content); + } + } + } +} diff --git a/Sdk/Http/HttpClientSync.cs b/Sdk/Http/HttpClientSync.cs new file mode 100644 index 0000000000000000000000000000000000000000..9652a3bca58a4057ff89eacb81ab6f539998d2d2 --- /dev/null +++ b/Sdk/Http/HttpClientSync.cs @@ -0,0 +1,105 @@ +using Signit.Sdk.Extentions; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; +using System.Net.Http; +using System.Web; +using Viyi.Util.Linq; + +namespace Signit.Sdk.Http +{ + partial class HttpClientSync + { + private const string USER_AGENT = "Signit HTTP"; + private const string DEFAULT_ENCODING = "UTF-8"; + + private static Response ConvertResponse(HttpResponseMessage response, bool needContentOnError = false) + { + var result = new Response() + { + IsSuccessStatusCode = response.IsSuccessStatusCode, + Code = response.StatusCode + }; + + if (needContentOnError || response.IsSuccessStatusCode) + { + result.Content = response.Content.ReadAsStringAsync().WaitResult(); + } + + Debug.WriteLine($"[RESPONSE] {result.Content}"); + return result; + } + + private NameValueCollection queryParams = new NameValueCollection(); + private Authentication authentication; + + public Response Get(string url) + { + url = ConcatQueryString(url); + + Debug.WriteLine($"[GET] {url}"); + HttpClient client = new HttpClient(); + var response = client.GetAsync(url).WaitResult(); + return ConvertResponse(response); + } + + public Response Post(string url, object payload, NameValueCollection headers = null) + { + url = ConcatQueryString(url); + url = ConcatQueryParam(url, "access_token", authentication.AccessToken); + + var content = new JsonContent(payload); + + HttpClient client = new HttpClient(); + client.DefaultRequestHeaders.Add("Accept", "application/json"); + client.DefaultRequestHeaders.Add("Accept-Encoding", DEFAULT_ENCODING); + client.DefaultRequestHeaders.Add("User-Agent", USER_AGENT); + + headers?.AllKeys.ForEach(key => + { + client.DefaultRequestHeaders.Add(key, headers[key]); + }); + + Debug.WriteLine($"[POST] {url}"); + var response = client.PostAsync(url, content).WaitResult(); + return ConvertResponse(response, true); + } + + + private static string ConcatQueryParam(string url, string key, string value) + { + var pair = $"{key}={HttpUtility.UrlEncode(value)}"; + return $"{url}{(url.Contains("?") ? "&" : "?")}{pair}"; + } + + private string ConcatQueryString(string url) + { + var pairs = queryParams.AllKeys + .SelectMany(key => + { + return queryParams.GetValues(key) + .Select(v => $"{key}={HttpUtility.UrlEncode(v)}"); + }); + var queryString = string.Join("&", pairs); + + if (!string.IsNullOrEmpty(queryString)) + { + url = $"{url}{(url.Contains("?") ? "&" : "?")}{queryString}"; + } + + return url; + } + + public HttpClientSync AddQueryParam(string name, string value) + { + queryParams.Add(name, value); + return this; + } + + public HttpClientSync WithAuthentication(Authentication auth) + { + authentication = auth; + return this; + } + } +} diff --git a/Sdk/Http/JsonContent.cs b/Sdk/Http/JsonContent.cs new file mode 100644 index 0000000000000000000000000000000000000000..28253da1e377ee0e543eb3a7c64ed01f223d3a72 --- /dev/null +++ b/Sdk/Http/JsonContent.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using System.Net.Http; +using System.Text; + +namespace Signit.Sdk.Http +{ + public class JsonContent : StringContent + { + public JsonContent(object model) : base(ToJson(model), Encoding.UTF8, "application/json") { } + + private static readonly JsonSerializerSettings defaultJsonSettings = CreateDefaultJsonSettings(); + + private static JsonSerializerSettings CreateDefaultJsonSettings() + { + var settings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + }; + + settings.Converters.Add(new StringEnumConverter()); + return settings; + } + + private static string ToJson(object model, JsonSerializerSettings settings = null) + { + return JsonConvert.SerializeObject(model, settings ?? defaultJsonSettings); + } + } +} diff --git a/Sdk/Http/OAuthData.cs b/Sdk/Http/OAuthData.cs new file mode 100644 index 0000000000000000000000000000000000000000..6fdfd42869ce8cf57d33e4a60469c995e9e90cba --- /dev/null +++ b/Sdk/Http/OAuthData.cs @@ -0,0 +1,31 @@ +namespace Signit.Sdk.Http +{ + public class OAuthData + { + public string access_token { private get; set; } + public string token_type { private get; set; } + public int expires_in { private get; set; } + public string scope { private get; set; } + + public string AccessToken + { + get => access_token; + set => access_token = value; + } + public string TokenType + { + get => token_type; + set => token_type = value; + } + public int ExpiresIn + { + get => expires_in; + set => expires_in = value; + } + public string Scope + { + get => scope; + set => scope = value; + } + } +} diff --git a/Sdk/Http/PersonVerifyResponse.cs b/Sdk/Http/PersonVerifyResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..dfd0d7a03884e9986a0cba7125a7b4f87bae8b58 --- /dev/null +++ b/Sdk/Http/PersonVerifyResponse.cs @@ -0,0 +1,10 @@ +namespace Signit.Sdk.Http +{ + /// + /// 个人实名认证响应. + /// + public class PersonVerifyResponse : AbstractSignitResponse + { + } + +} diff --git a/Sdk/Http/SignatureRequest.cs b/Sdk/Http/SignatureRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..0d99b8edf996895cde30988484dd87a308d02fee --- /dev/null +++ b/Sdk/Http/SignatureRequest.cs @@ -0,0 +1,11 @@ +using Signit.Sdk.Entity; +using System.Collections.Generic; + +namespace Signit.Sdk.Http +{ + public class SignatureRequest : AbstractSignitRequest + { + public FileData FileData; + public IList SignDetails; + } +} diff --git a/Sdk/Http/SignatureResponse.cs b/Sdk/Http/SignatureResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..5be40c9df449bc04a6ed8a340c26e8c9657a0d6f --- /dev/null +++ b/Sdk/Http/SignatureResponse.cs @@ -0,0 +1,6 @@ +namespace Signit.Sdk.Http +{ + public class SignatureResponse : AbstractSignitResponse + { + } +} diff --git a/Sdk/Http/Webhook/AbstractWebhookResponse.cs b/Sdk/Http/Webhook/AbstractWebhookResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..a15de35f1812d7826bb48cd4925ccc82b0273bf5 --- /dev/null +++ b/Sdk/Http/Webhook/AbstractWebhookResponse.cs @@ -0,0 +1,6 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class AbstractWebhookResponse : AbstractSignitResponse + { + } +} diff --git a/Sdk/Http/Webhook/BoContact.cs b/Sdk/Http/Webhook/BoContact.cs new file mode 100644 index 0000000000000000000000000000000000000000..814998b813575338997dd130fb3dc8d154080c43 --- /dev/null +++ b/Sdk/Http/Webhook/BoContact.cs @@ -0,0 +1,14 @@ +namespace Signit.Sdk.Http.Webhook +{ + /** + * 联系方式详细数据. + * + * @since 2.0.0 + */ + public class BoContact + { + public string AuthorWsid { get; set; } + public string Email { get; set; } + public string Sms { get; set; } + } +} diff --git a/Sdk/Http/Webhook/BoContactMetadata.cs b/Sdk/Http/Webhook/BoContactMetadata.cs new file mode 100644 index 0000000000000000000000000000000000000000..f96b0f0ab40a99e8c2732c6012d42c00268fb976 --- /dev/null +++ b/Sdk/Http/Webhook/BoContactMetadata.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Signit.Sdk.Http.Webhook +{ + /** + * 联系方式元数据. + * + * @since 2.0.0 + */ + public class BoContactMetadata + { + public IList Contacts { get; set; } + } +} diff --git a/Sdk/Http/Webhook/EnterpriseVerificationCompleted.cs b/Sdk/Http/Webhook/EnterpriseVerificationCompleted.cs new file mode 100644 index 0000000000000000000000000000000000000000..57117ee5856bf1e7b349928c9d171ebc1aadd838 --- /dev/null +++ b/Sdk/Http/Webhook/EnterpriseVerificationCompleted.cs @@ -0,0 +1,6 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class EnterpriseVerificationCompleted : EnterpriseVerificationPrimaryCompleted + { + } +} diff --git a/Sdk/Http/Webhook/EnterpriseVerificationPaid.cs b/Sdk/Http/Webhook/EnterpriseVerificationPaid.cs new file mode 100644 index 0000000000000000000000000000000000000000..1c1bd1d4344ce4196f2ff88e656d17bc412d2330 --- /dev/null +++ b/Sdk/Http/Webhook/EnterpriseVerificationPaid.cs @@ -0,0 +1,6 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class EnterpriseVerificationPaid : EnterpriseVerificationPrimaryCompleted + { + } +} diff --git a/Sdk/Http/Webhook/EnterpriseVerificationPrimaryCompleted.cs b/Sdk/Http/Webhook/EnterpriseVerificationPrimaryCompleted.cs new file mode 100644 index 0000000000000000000000000000000000000000..16ec1beeb41b85437777f0648f0c41baa8c0cd5c --- /dev/null +++ b/Sdk/Http/Webhook/EnterpriseVerificationPrimaryCompleted.cs @@ -0,0 +1,10 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class EnterpriseVerificationPrimaryCompleted : AbstractWebhookResponse + { + public string Description { get; set; } + public string Status { get; set; } + public long? HandleDatetime { get; set; } + public long? SubmitDatetime { get; set; } + } +} diff --git a/Sdk/Http/Webhook/EnterpriseVerificationSubmitted.cs b/Sdk/Http/Webhook/EnterpriseVerificationSubmitted.cs new file mode 100644 index 0000000000000000000000000000000000000000..4de3edd57dd81f76ad0c85c4617e91c9a86dc10e --- /dev/null +++ b/Sdk/Http/Webhook/EnterpriseVerificationSubmitted.cs @@ -0,0 +1,8 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class EnterpriseVerificationSubmitted : AbstractWebhookResponse + { + private string Status {get;set;} + private string ActionUrl {get;set;} + } +} diff --git a/Sdk/Http/Webhook/EnvelopeCompleted.cs b/Sdk/Http/Webhook/EnvelopeCompleted.cs new file mode 100644 index 0000000000000000000000000000000000000000..d9885b84669d52bcd0188183936622658740ba74 --- /dev/null +++ b/Sdk/Http/Webhook/EnvelopeCompleted.cs @@ -0,0 +1,6 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class EnvelopeCompleted : EnvelopeStarted + { + } +} diff --git a/Sdk/Http/Webhook/EnvelopeStarted.cs b/Sdk/Http/Webhook/EnvelopeStarted.cs new file mode 100644 index 0000000000000000000000000000000000000000..532a2812c03517e05d11d7a6088f1dea6d70b26d --- /dev/null +++ b/Sdk/Http/Webhook/EnvelopeStarted.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Signit.Sdk.Http.Webhook +{ + public class EnvelopeStarted : AbstractWebhookResponse + { + // 执行的动作对应的URL地址 + public string ActionUrl { get; set; } + + // 流程允许在易企签WEB执行的动作 + public IList Actions { get; set; } + + // 接收方对应的在易企签的帐号 + public string Account { get; set; } + + // 信封基本信息 + public RawDataBasicInfo BasicEnvelope { get; set; } + + public Sender SenderParticipant { get; set; } + public Receiver ReceiverParticipant { get; set; } + + // 已完成的签署数据 + public SignData SignData { get; set; } + + // 客户端回调地址 + public string ReturnUrl { get; set; } + } +} diff --git a/Sdk/Http/Webhook/ParticipantConfirmed.cs b/Sdk/Http/Webhook/ParticipantConfirmed.cs new file mode 100644 index 0000000000000000000000000000000000000000..9eb5459d12856769affc39875a78f9e1522c7b63 --- /dev/null +++ b/Sdk/Http/Webhook/ParticipantConfirmed.cs @@ -0,0 +1,6 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class ParticipantConfirmed : EnvelopeStarted + { + } +} diff --git a/Sdk/Http/Webhook/ParticipantHandling.cs b/Sdk/Http/Webhook/ParticipantHandling.cs new file mode 100644 index 0000000000000000000000000000000000000000..670f379c9aa56c44f16cfcd2379431cc90717b3a --- /dev/null +++ b/Sdk/Http/Webhook/ParticipantHandling.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace Signit.Sdk.Http.Webhook +{ + public class ParticipantHandling : AbstractWebhookResponse + { + // 执行的动作对应的URL地址 + public string ActionUrl { get; set; } + + // 流程允许在易企签WEB执行的动作 + public IList Actions { get; set; } + + // 接收方对应的在易企签的帐号 + public string Account { get; set; } + + // 信封基本信息 + public RawDataBasicInfo BasicEnvelope { get; set; } + + public Sender SenderParticipant { get; set; } + public Receiver ReceiverParticipant { get; set; } + + // 已完成的签署数据 + public SignData SignData { get; set; } + + // 客户端回调地址 + public string ReturnUrl { get; set; } + } + +} diff --git a/Sdk/Http/Webhook/ParticipantRejected.cs b/Sdk/Http/Webhook/ParticipantRejected.cs new file mode 100644 index 0000000000000000000000000000000000000000..ee4982a9e25e677636b80f43032538a609d801b7 --- /dev/null +++ b/Sdk/Http/Webhook/ParticipantRejected.cs @@ -0,0 +1,7 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class ParticipantRejected : EnvelopeStarted + { + + } +} diff --git a/Sdk/Http/Webhook/PersonVerificationCompleted.cs b/Sdk/Http/Webhook/PersonVerificationCompleted.cs new file mode 100644 index 0000000000000000000000000000000000000000..c7faf834e308092b338d56fc5b49f4848a091de3 --- /dev/null +++ b/Sdk/Http/Webhook/PersonVerificationCompleted.cs @@ -0,0 +1,12 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class PersonVerificationCompleted : AbstractWebhookResponse + { + public string Description { get; set; } + public string Status { get; set; } + public long? HandleDatetime { get; set; } + public long? SubmitDatetime { get; set; } + + } + +} diff --git a/Sdk/Http/Webhook/PersonVerificationSubmitted.cs b/Sdk/Http/Webhook/PersonVerificationSubmitted.cs new file mode 100644 index 0000000000000000000000000000000000000000..f1526a83ed67e0fea7734149465913d784827e7d --- /dev/null +++ b/Sdk/Http/Webhook/PersonVerificationSubmitted.cs @@ -0,0 +1,8 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class PersonVerificationSubmitted : EnterpriseVerificationPrimaryCompleted + { + + } + +} diff --git a/Sdk/Http/Webhook/QuickSignCompleted.cs b/Sdk/Http/Webhook/QuickSignCompleted.cs new file mode 100644 index 0000000000000000000000000000000000000000..9607b3118d216062a4bc48ae3d3a66df8682af07 --- /dev/null +++ b/Sdk/Http/Webhook/QuickSignCompleted.cs @@ -0,0 +1,8 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class QuickSignCompleted : AbstractWebhookResponse + { + public string Url { get; set; } + public string Base64 { get; set; } + } +} diff --git a/Sdk/Http/Webhook/RawDataBasicInfo.cs b/Sdk/Http/Webhook/RawDataBasicInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..a68643cba4a596db80773fce36e1fdac7e3b268d --- /dev/null +++ b/Sdk/Http/Webhook/RawDataBasicInfo.cs @@ -0,0 +1,31 @@ +namespace Signit.Sdk.Http.Webhook +{ + /** + * 信封基本信息. + * + * @since 2.0.0 + */ + public class RawDataBasicInfo + { + // 信封全局唯一ID + public string Wsid { get; set; } + + // 信封状态 + public string Status { get; set; } + + // 信封创建时间 + public long? CreatedDatetime { get; set; } + + // 信封过期时间 + public long? ExpireDatetime { get; set; } + + // 信封最新状态时间 + public long? StatusDatetime { get; set; } + + // 信封最新状态原因 + public string StatusReason { get; set; } + + // 当前签署方的序号 + public int? CurrentSequence { get; set; } + } +} diff --git a/Sdk/Http/Webhook/Receiver.cs b/Sdk/Http/Webhook/Receiver.cs new file mode 100644 index 0000000000000000000000000000000000000000..3708bb66b440eee9ee519b00e6d943ce8787ff0d --- /dev/null +++ b/Sdk/Http/Webhook/Receiver.cs @@ -0,0 +1,56 @@ +namespace Signit.Sdk.Http.Webhook +{ + /** + * 当前需要签署的接收方信息. + * + * @since 2.0.0 + */ + public class Receiver + { + // 接收方信息 + public string Name { get; set; } + + // 接收方联系方式 + public BoContactMetadata ContactMetadata { get; set; } + + // 安全级别 + public string SecureLevel { get; set; } + + // 接收方类型 + public string Type { get; set; } + + // 接收方角色类型 + public string RoleType { get; set; } + + // 接收方是否必须预分配表单域 + public bool NeedForm { get; set; } + + // 设置接收方私人信息 + public string AssignedMessage { get; set; } + + // 设置接收方处理顺序 + public int? AssignedSequence { get; set; } + + // 签署认证等级 + public string AuthLevel { get; set; } + + // 接收方所在企业名称 + public string EnterpriseName { get; set; } + + // 信封自定义元数据信息 + public string Metadata { get; set; } + + // 签署状态 + public string Status { get; set; } + + // 接收方全局唯一标识 + public string Wsid { get; set; } + + // 处理时间 + public long? HandleDatetime { get; set; } + + // 处理原因 + public string HandleReason { get; set; } + + } +} diff --git a/Sdk/Http/Webhook/Sender.cs b/Sdk/Http/Webhook/Sender.cs new file mode 100644 index 0000000000000000000000000000000000000000..2561eb49b5cc9f59bde869ec5ebc19c46cf615f2 --- /dev/null +++ b/Sdk/Http/Webhook/Sender.cs @@ -0,0 +1,18 @@ +namespace Signit.Sdk.Http.Webhook +{ + /** + * 发送方信息. + * + * @since 2.0.0 + */ + public class Sender + { + // 和Receiver同级 + // 发送者姓名 + public string Name { get; set; } + + // 发送者联系方式 + public BoContactMetadata ContactMetadata { get; set; } + + } +} diff --git a/Sdk/Http/Webhook/SignData.cs b/Sdk/Http/Webhook/SignData.cs new file mode 100644 index 0000000000000000000000000000000000000000..0a8d084b4e02cf3188df7dc848e61cabbf5c52b8 --- /dev/null +++ b/Sdk/Http/Webhook/SignData.cs @@ -0,0 +1,21 @@ +namespace Signit.Sdk.Http.Webhook +{ + /** + * 已成功完成签署流程的文件数据. + * + * + * @since 2.0.0 + */ + public class SignData + { + // 文件ID + public string FileWsid { get; set; } + + // 已签署的文件数据的URL地址 + public string Url { get; set; } + + // 已签署的文件数据数据的base64表示形式的字符串 + public string Base64 { get; set; } + + } +} diff --git a/Sdk/Http/Webhook/WebhookData.cs b/Sdk/Http/Webhook/WebhookData.cs new file mode 100644 index 0000000000000000000000000000000000000000..f0377946d66afd22434fd808e2b809d239517165 --- /dev/null +++ b/Sdk/Http/Webhook/WebhookData.cs @@ -0,0 +1,7 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class WebhookData : WebhookDataBase + { + public SignatureResponse RawData { get; set; } + } +} diff --git a/Sdk/Http/Webhook/WebhookDataBase.cs b/Sdk/Http/Webhook/WebhookDataBase.cs new file mode 100644 index 0000000000000000000000000000000000000000..e93638470b4ea179a77bab4897cf50812e9711b9 --- /dev/null +++ b/Sdk/Http/Webhook/WebhookDataBase.cs @@ -0,0 +1,22 @@ +namespace Signit.Sdk.Http.Webhook +{ + public class WebhookDataBase + { + public string Event { get; set; } + public SourceType Source { get; set; } + public TargetType Target { get; set; } + public bool NeedCallback { get; set; } + + public class SourceType + { + public string Platform { get; set; } + public string Destination { get; set; } + } + + public class TargetType + { + public string WebhookWsid { get; set; } + public string Destination { get; set; } + } + } +} diff --git a/Sdk/Http/Webhook/WebhookResponse.cs b/Sdk/Http/Webhook/WebhookResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..39503d9831a99219b39af5ae7a24cd6a2d64aaf3 --- /dev/null +++ b/Sdk/Http/Webhook/WebhookResponse.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; +using System; + +namespace Signit.Sdk.Http.Webhook +{ + public class WebhookResponse : WebhookDataBase + { + public string RawData { get; set; } + + public AbstractWebhookResponse GetResponseData() + { + return JsonConvert.DeserializeObject(RawData, GetEventType()) as AbstractWebhookResponse; + } + + public T GetResponseData() + where T : AbstractWebhookResponse + { + return JsonConvert.DeserializeObject(RawData); + } + + private Type GetEventType() + { + var @event = Event; + var fullName = $"Signit.Sdk.Http.Webhook.{@event}"; + + try + { + var type = Type.GetType(fullName, true, true); + if (!typeof(AbstractWebhookResponse).IsAssignableFrom(type)) + { + throw new SignitException("无法根据事件名称[%s]自动生成对应的webhook响应数据[%s],请选择重新解析方法"); + } + return type; + } + catch (TypeLoadException tle) + { + throw new SignitException( + "无法根据事件名称[%s]自动生成对应的webhook响应数据[%s],请选择重新解析方法", + tle); + } + } + } +} diff --git a/Sdk/IVerifyRequest.cs b/Sdk/IVerifyRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..819ea9e2d04e4ec19fe422c217fb936135e50de9 --- /dev/null +++ b/Sdk/IVerifyRequest.cs @@ -0,0 +1,11 @@ +namespace Signit.Sdk +{ + public interface IVerifyRequest + { + string GetHeader(string key); + string GetMethod(); + string ContentType { get; } + + byte[] GetBody(); + } +} diff --git a/Sdk/Properties/AssemblyInfo.cs b/Sdk/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..4a7bf64a80a2b6547324dd884f996f731a74fd03 --- /dev/null +++ b/Sdk/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("Sdk")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("SignIt")] +[assembly: AssemblyProduct("Sdk")] +[assembly: AssemblyCopyright("Copyright © SignIt 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("e583eaf3-04f9-4ab6-b265-d737568aaefc")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Sdk/Sdk.csproj b/Sdk/Sdk.csproj new file mode 100644 index 0000000000000000000000000000000000000000..6417192fe6cf1661bde441a8c4ec538606e10ebe --- /dev/null +++ b/Sdk/Sdk.csproj @@ -0,0 +1,181 @@ + + + + + Debug + AnyCPU + {E583EAF3-04F9-4AB6-B265-D737568AAEFC} + Library + Properties + Signit.Sdk + Signit.Sdk + v4.0 + 512 + true + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net40\Newtonsoft.Json.dll + + + + + ..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.IO.dll + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net40\System.Net.Http.dll + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net40\System.Net.Http.Extensions.dll + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net40\System.Net.Http.Primitives.dll + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net40\System.Net.Http.WebRequest.dll + + + ..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Runtime.dll + + + ..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Threading.Tasks.dll + + + + + + + + + ..\packages\Viyi.Util.1.1.2\lib\net40\Viyi.Util.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 + + + + \ No newline at end of file diff --git a/Sdk/SignitClient.cs b/Sdk/SignitClient.cs new file mode 100644 index 0000000000000000000000000000000000000000..02e1cf20d59c066093596279f807b47a20972fc4 --- /dev/null +++ b/Sdk/SignitClient.cs @@ -0,0 +1,195 @@ +using Newtonsoft.Json; +using Signit.Sdk.Http; +using Signit.Sdk.Http.Webhook; +using Signit.Sdk.Types; +using Signit.Sdk.Util; +using System.Collections.Specialized; +using System.Diagnostics; +using Viyi.Util; + +namespace Signit.Sdk +{ + public class SignitClient + { + public static WebhookData ParseWebhookData(string raw) + { + return JsonConvert.DeserializeObject(raw); + } + + public static WebhookResponse ParseWebhookResponse(string raw) + { + return JsonConvert.DeserializeObject(raw); + } + + public Authentication Authentication { get; private set; } + private readonly ApiOptions options; + + //private HttpClientWrapper httpClient; + //private readonly int MAX_COUNT = 3; + //private readonly AtomicInteger count = new AtomicInteger(MAX_COUNT); + //private const Pattern LEFT_QUOTATION = Pattern.compile("\"\\{"); + //private const Pattern RIGHT_QUOTATION = Pattern.compile("\\}\""); + //private const Pattern BACKLASH_QUOTATION = Pattern.compile("\\\\\""); + + public string BaseUrl { get; set; } + public string OAuthTokenUrl { get; set; } + + public SignitClient(ApiOptions options = null) : this(null, null, options) { } + + public SignitClient(string appId, string secretKey, string url) + : this(new Authentication() { AppId = appId, SecuretKey = secretKey }, + url) + { } + + public SignitClient(Authentication auth, string url = null, ApiOptions options = null) + { + this.options = options = options ?? ApiOptions.Default; + options.EnvUrl = url ?? options.EnvUrl; + SetUrl(options.EnvUrl); + Authentication = auth; + } + + public string Url { set => SetUrl(value); } + public string EnvUrl => options.EnvUrl; + + public SignitClient SetUrl(string envUrl) + { + var url = string.IsNullOrWhiteSpace(envUrl) + ? options.EnvUrl + : envUrl.Trim(); + + BaseUrl = $"{url}{options.BaseApiUrl}"; + OAuthTokenUrl = $"{url}{options.OAuthTokenUrl}"; + + return this; + } + + /// + /// 单纯的进行认证并获取授权相关信息,并不保存 + /// + /// + /// + /// + /// + public OAuthData RequestAuthentication(string apiKey, string secretKey, + TokenType tokenType = TokenType.CLIENT_CREDENTIALS) + { + if (string.IsNullOrEmpty(apiKey) || string.IsNullOrEmpty(secretKey)) + { + throw new SignitException("请完善开发者账户数据"); + } + + HttpClientSync client = new HttpClientSync() + .AddQueryParam(options.ClientId, apiKey) + .AddQueryParam(options.ClientSecret, secretKey) + .AddQueryParam(options.GrantType, tokenType.ToString().ToLower()); + + return client.Get(OAuthTokenUrl).GetObject(); + } + + /// + /// 授权并缓存获取的授权信息 + /// + /// + /// + /// + /// + public Authentication Authenticate(string apiKey, string secretKey, + TokenType tokenType = TokenType.CLIENT_CREDENTIALS) + { + OAuthData authData = RequestAuthentication(apiKey, secretKey, tokenType); + if (authData == null) + { + return null; + } + + Authentication = new Authentication + { + AccessToken = authData.AccessToken, + AccessTokenType = tokenType, + AppId = apiKey, + SecuretKey = secretKey + }; + + return Authentication; + } + + public T Execute(AbstractSignitRequest request) + where T : AbstractSignitResponse + { + if (request == null) + { + return null; + } + + var auth = Authentication; + if (auth == null || !(auth.HasAppId && auth.HasSecretKey)) + { + throw new SignitException("请完善开发者信息"); + } + + if (!auth.HasAccessToken) + { + Authenticate(auth.AppId, auth.SecuretKey, auth.AccessTokenType); + } + + var client = new HttpClientSync(); + var headers = new NameValueCollection + { + { options.AppIdKey, auth.AppId } + }; + + var response = client + .WithAuthentication(Authentication) + .Post(BaseUrl, request, headers); + + var result = response.GetObject(); + if (!result.IsSuccess) + { + result.Error = response.GetObject(); + } + return result; + } + + /// + /// 发送快捷签署请求 + /// + /// 快捷签署请求 + /// 快捷签署响应 + public SignatureResponse QuickSign(SignatureRequest request) + { + return Execute(request); + } + + public static bool Verify(string signitSignature, string appId, HmacSignatureBuilder builder) + { + if (builder == null || appId == null || signitSignature == null) + { + return false; + } + + string selfSignature = $"{HmacSignatureBuilder.DEFAULT_ALGORITHM} {appId}:{builder.BuildAsBase64()}"; + return selfSignature == signitSignature; + } + + public static bool Verify(string appId, string secretKey, IVerifyRequest request) + { + var signature = request.GetHeader("X-Signit-Signature") ?? ""; + + HmacSignatureBuilder builder = new HmacSignatureBuilder() + .Scheme(request.GetHeader("X-Signit-Scheme")) + .ApiKey(appId) + .ApiSecret(secretKey.ToByteArray()) + .Method(request.GetMethod().ToUpper()) + .Payload(request.GetBody()) + .ContentType(request.ContentType) + .Host(request.GetHeader("X-Signit-Host") ?? "") + .Resource(request.GetHeader("X-Signit-Resource") ?? "") + .Nonce(request.GetHeader("X-Signit-Nonce") ?? "") + .Date(request.GetHeader("X-Signit-Date") ?? ""); + + string selfSignature = $"{HmacSignatureBuilder.DEFAULT_ALGORITHM} {appId}:{builder.BuildAsBase64()}"; + return selfSignature == signature; + } + } +} diff --git a/Sdk/SignitException.cs b/Sdk/SignitException.cs new file mode 100644 index 0000000000000000000000000000000000000000..8bfea0ca21bc7392d71aca1ec7bde1e3c264e8c0 --- /dev/null +++ b/Sdk/SignitException.cs @@ -0,0 +1,35 @@ +using System; + +namespace Signit.Sdk +{ + public class SignitException : Exception + { + private readonly int httpCode; + private readonly string type; + + public SignitException(Exception e) : base(null, e) { } + + public SignitException(string message) : base(message) { } + + public SignitException(string message, Exception e) : base(message, e) { } + + public SignitException(string message, int httpCode, string type) + : this(message, httpCode, type, null) { } + + public SignitException(string message, int httpCode, string type, Exception e) : base(message, e) + { + this.httpCode = httpCode; + this.type = type; + } + + public int getHttpCode() + { + return httpCode; + } + + public string getType() + { + return type; + } + } +} diff --git a/Sdk/Types/AcceptDataType.cs b/Sdk/Types/AcceptDataType.cs new file mode 100644 index 0000000000000000000000000000000000000000..eb6d616f8a723bae184611a91c2da20a744e2a30 --- /dev/null +++ b/Sdk/Types/AcceptDataType.cs @@ -0,0 +1,7 @@ +namespace Signit.Sdk.Types +{ + public enum AcceptDataType + { + BASE64, URL + } +} diff --git a/Sdk/Types/AuthLevel.cs b/Sdk/Types/AuthLevel.cs new file mode 100644 index 0000000000000000000000000000000000000000..fac3ab47fc886d762d7c76dea3f6a6d078ec919d --- /dev/null +++ b/Sdk/Types/AuthLevel.cs @@ -0,0 +1,24 @@ +namespace Signit.Sdk.Types +{ + + /// + /// 参与者签署认证等级枚举 + /// + public enum AuthLevel + { + /// + /// 低等级 + /// + LOW, + + /// + /// 中等级 + /// + MIDDLE, + + /// + /// 高等级 + /// + HIGH + } +} diff --git a/Sdk/Types/AuthType.cs b/Sdk/Types/AuthType.cs new file mode 100644 index 0000000000000000000000000000000000000000..19aa5eb5c5f4762072b9791c8126267671e047c6 --- /dev/null +++ b/Sdk/Types/AuthType.cs @@ -0,0 +1,51 @@ +namespace Signit.Sdk.Types +{ + + /** + * 签署认证类型类型. + * + * 用户确认签署时,需要验证签署人身份,此处为所有验证方式枚举 + * + * @since 2.1.0 + */ + public enum AuthType + { + /** + * 短信验证码 + * + * @since 2.1.0 + */ + SMS_CODE, + /** + * 邮箱验证码 + * + * @since 2.1.0 + */ + EMAIL_CODE, + /** + * 签署密码 + * + * @since 2.1.0 + */ + SIGN_PIN, + /** + * 活体认证 + * + * @since 2.1.0 + */ + LIVING_AUTH, + /** + * usbkey认证 + * + * @since 2.1.0 + */ + USBKEY, + /** + * 手机盾认证 + * + * @since 2.1.0 + */ + MOBILE_SHIELD + + } +} \ No newline at end of file diff --git a/Sdk/Types/CertType.cs b/Sdk/Types/CertType.cs new file mode 100644 index 0000000000000000000000000000000000000000..262d0d3a1e2da366e9f6a4ef875d66a0e7e0dc63 --- /dev/null +++ b/Sdk/Types/CertType.cs @@ -0,0 +1,22 @@ +namespace Signit.Sdk.Types +{ + + /** + * + * 签名所使用的证书类型枚举. + * + * @since 2.0.0 + */ + public enum CertType + { + + /** + * 一次性证书 + */ + DISPOSABLE_CERT, + /** + * 标准证书 + */ + STANDARD_CERT + } +} \ No newline at end of file diff --git a/Sdk/Types/Direction.cs b/Sdk/Types/Direction.cs new file mode 100644 index 0000000000000000000000000000000000000000..12f38f29daf3ba44df256fc5ca6cf3e3fd30fe68 --- /dev/null +++ b/Sdk/Types/Direction.cs @@ -0,0 +1,8 @@ +namespace Signit.Sdk.Types +{ + + public enum Direction + { + TOP, BOTTOM, LEFT, RIGHT, CENTER + } +} \ No newline at end of file diff --git a/Sdk/Types/EnterpriseAuthType.cs b/Sdk/Types/EnterpriseAuthType.cs new file mode 100644 index 0000000000000000000000000000000000000000..e327256d3558e5cb7c47175580bcf5d6d2398c4f --- /dev/null +++ b/Sdk/Types/EnterpriseAuthType.cs @@ -0,0 +1,21 @@ +namespace Signit.Sdk.Types + +{ + /** + * + * 企业实名认证类型. + * + * @since 1.0.2 + */ + public enum EnterpriseAuthType + { + /** + * 法人认证 + */ + LEGAL_PERSON, + /** + * 经办人认证 + */ + AGENT + } +} \ No newline at end of file diff --git a/Sdk/Types/EnvelopeRoleType.cs b/Sdk/Types/EnvelopeRoleType.cs new file mode 100644 index 0000000000000000000000000000000000000000..953c4f9df8011f7612a7f7aeff3a0249a4cfa744 --- /dev/null +++ b/Sdk/Types/EnvelopeRoleType.cs @@ -0,0 +1,20 @@ +namespace Signit.Sdk.Types +{ + /** + * + * 接收方角色类型. + * + * @since 1.0.2 + */ + public enum EnvelopeRoleType + { + /** + * 个人 + */ + PERSON, + /** + * 企业成员 + */ + ENTERPRISE_MEMBER + } +} \ No newline at end of file diff --git a/Sdk/Types/EnvelopeType.cs b/Sdk/Types/EnvelopeType.cs new file mode 100644 index 0000000000000000000000000000000000000000..db3d31827b03a537095c33505a77ca30110fff8f --- /dev/null +++ b/Sdk/Types/EnvelopeType.cs @@ -0,0 +1,36 @@ +namespace Signit.Sdk.Types +{ + + /** + * 信封类型枚举. + * + * @since 1.0.2 + */ + public enum EnvelopeType + { + /** + * 任意. + * + * @since 1.0.2 + */ + ANY, + /** + * 本人. + * + * @since 1.0.2 + */ + ME, + /** + * 我与他人. + * + * @since 1.0.2 + */ + ME_AND_OTHERS, + /** + * 他人. + * + * @since 1.0.2 + */ + OTHERS + } +} \ No newline at end of file diff --git a/Sdk/Types/FileType.cs b/Sdk/Types/FileType.cs new file mode 100644 index 0000000000000000000000000000000000000000..631af393eb6b4bd80e65da7747cc8577e871bd04 --- /dev/null +++ b/Sdk/Types/FileType.cs @@ -0,0 +1,174 @@ +namespace Signit.Sdk.Types +{ + /** + * 基于文件头描述源文件真实类型. + * + * @since 2.0.0 + */ + public enum FileType + { + /** + * JPEG. + */ + JPEG, + + /** + * JPG. + */ + JPG, + + /** + * JPE. + */ + JPE, + + /** + * PNG. + */ + PNG, + + /** + * GIF. + */ + GIF, + + /** + * TIFF. + */ + TIFF, + + /** + * Windows Bitmap. + */ + BMP, + + /** + * CAD. + */ + DWG, + + /** + * Adobe Photoshop. + */ + PSD, + + /** + * Rich Text Format. + */ + RTF, + + /** + * XML. + */ + XML, + + /** + * HTML. + */ + HTML, + + /** + * Email [thorough only]. + */ + EML, + + /** + * Outlook Express. + */ + DBX, + + /** + * Outlook (pst). + */ + PST, + + /** + * MS 2003 Word/Excel/PointPower. + */ + MS_2003, + + /** + * MS 2007 and later Archive.MS Word/Excel/PointPower. (docx,pptx,xlsx) + */ + MS_2007, + + /** + * MS Access. + */ + MDB, + + /** + * WordPerfect. + */ + WPD, + + /** + * Postscript. + */ + EPS, + + /** + * Adobe Acrobat. + */ + PDF, + + /** + * Quicken. + */ + QDF, + + /** + * Windows Password. + */ + PWL, + + /** + * ZIP Archive. (docx,pptx,xlsx) + */ + ZIP, + + /** + * RAR Archive. + */ + RAR, + + /** + * Wave. + */ + WAV, + /** + * AVI. + */ + AVI, + + /** + * Real Audio. + */ + RAM, + + /** + * Real Media. + */ + RM, + + /** + * MPEG (mpg). + */ + MPG, + + /** + * Quicktime. + */ + MOV, + + /** + * Windows Media. + */ + ASF, + + /** + * MIDI. + */ + MID + } +} diff --git a/Sdk/Types/FormType.cs b/Sdk/Types/FormType.cs new file mode 100644 index 0000000000000000000000000000000000000000..074c598daae92fa9bdff12af3388eb39b02af179 --- /dev/null +++ b/Sdk/Types/FormType.cs @@ -0,0 +1,33 @@ +namespace Signit.Sdk.Types +{ + /** + * + * 表单类型枚举. + * + * @since 2.0.0 + */ + public enum FormType + { + + /** + * 印章签名表单域 + */ + SEAL_SIGN, + /** + * 手写签名表单域 + */ + WRITE_SIGN, + /** + * 文本表单域 + */ + TEXT, + /** + * 骑缝章表单域 + */ + MULTI_CHECK_MARK, + /** + * 二维码骑缝章表单域 + */ + MULTI_QRCODE_MARK + } +} \ No newline at end of file diff --git a/Sdk/Types/IdCardType.cs b/Sdk/Types/IdCardType.cs new file mode 100644 index 0000000000000000000000000000000000000000..ae9990fdd4994d027b136488ca9b4e8b67d89716 --- /dev/null +++ b/Sdk/Types/IdCardType.cs @@ -0,0 +1,25 @@ +namespace Signit.Sdk.Types +{ + /** + * + * 身份证类型枚举. + * + * @since 1.0.2 + */ + public enum IdCardType + { + /** + * 二代身份证 + */ + SECOND_GENERATION_IDCARD, + /** + * 临时身份证 + */ + TEMPORARY_IDCARD, + /** + * 护照 + */ + PASSPORT + + } +} \ No newline at end of file diff --git a/Sdk/Types/NamingStyle.cs b/Sdk/Types/NamingStyle.cs new file mode 100644 index 0000000000000000000000000000000000000000..23138ce82f493108676e00b1201be8c26fd74182 --- /dev/null +++ b/Sdk/Types/NamingStyle.cs @@ -0,0 +1,31 @@ +namespace Signit.Sdk.Types +{ + /** + * 命名风格的枚举. + * + * @author zhd + * @since 1.0.0 + */ + public enum NamingStyle + { + /** + * 小驼峰命名风格.如:nameStyle + */ + CAMEL, + + /** + * 字母间均大写命名风格.如:NameStyle + */ + PASCAL, + + /** + * 字母间均以下划线分隔命名风格(unix命名风格).如:name_style + */ + SNAKE, + + /** + * 字母间均以连词符分隔命名风格.如:name-style + */ + KEBAB + } +} diff --git a/Sdk/Types/ParticipantHandleMode.cs b/Sdk/Types/ParticipantHandleMode.cs new file mode 100644 index 0000000000000000000000000000000000000000..b73840cc6464a21b84902dfba6f32cfe385f7f03 --- /dev/null +++ b/Sdk/Types/ParticipantHandleMode.cs @@ -0,0 +1,25 @@ +namespace Signit.Sdk.Types +{ + /** + * 参与者处理表单的模式枚举 + * + * @since 2.0.0 + */ + public enum ParticipantHandleMode + { + /** + * 正常处理模式(默认). + * + * @since 2.0.0 + */ + NORMAL, + /** + * 静默处理模式. + * 该接受方不用执行签署流程,自动完成签署。接收方设置为静默签署条件:①接收方类型为签署者SIGNER;②接收方和发起方为同一企业下的同一人;③该接收方有预设表单,且该接收方的预设表单均设置了初始值(手写签名表单除外)。 + * + * @since 2.0.0 + */ + SILENCE + + } +} \ No newline at end of file diff --git a/Sdk/Types/PersonAuthType.cs b/Sdk/Types/PersonAuthType.cs new file mode 100644 index 0000000000000000000000000000000000000000..aa78860ee100b996bc5a954a74214b5ade1e6469 --- /dev/null +++ b/Sdk/Types/PersonAuthType.cs @@ -0,0 +1,16 @@ +namespace Signit.Sdk.Types +{ + /** + * 个人实名认证方式 + * + * @since 2.1.0 + */ + public enum PersonAuthType + { + /** + * 三网手机号三元素实名认证方式 + */ + PHONE_AUTH + + } +} \ No newline at end of file diff --git a/Sdk/Types/ReceiverType.cs b/Sdk/Types/ReceiverType.cs new file mode 100644 index 0000000000000000000000000000000000000000..125df206ace5d40849995d0699912ef440a2975b --- /dev/null +++ b/Sdk/Types/ReceiverType.cs @@ -0,0 +1,23 @@ +namespace Signit.Sdk.Types +{ + /** + * 参与者类型状态枚举. + * + * @since 2.0.0 + */ + public enum ReceiverType + { + /** + * 签署者 + * + * @since 2.0.0 + */ + SIGNER, + /** + * 审核者 + * + * @since 2.0.0 + */ + CHECKER, + } +} \ No newline at end of file diff --git a/Sdk/Types/RenderMode.cs b/Sdk/Types/RenderMode.cs new file mode 100644 index 0000000000000000000000000000000000000000..22ea1ec98d473faf4b4816f103bcbb952a731da2 --- /dev/null +++ b/Sdk/Types/RenderMode.cs @@ -0,0 +1,29 @@ +namespace Signit.Sdk.Types +{ + /** + * 签名渲染模式. + * + * + * @since 2.0.0 + */ + public enum RenderMode + { + + /** + * The rendering mode is just the description. + */ + DESCRIPTION, + /** + * The rendering mode is the name of the signer and the description. + */ + NAME_AND_DESCRIPTION, + /** + * The rendering mode is an image and the description. + */ + GRAPHIC_AND_DESCRIPTION, + /** + * The rendering mode is just an image. + */ + GRAPHIC + } +} \ No newline at end of file diff --git a/Sdk/Types/SecureLevel.cs b/Sdk/Types/SecureLevel.cs new file mode 100644 index 0000000000000000000000000000000000000000..3cba61e95d326b328412af2ddf55a9a6973c90df --- /dev/null +++ b/Sdk/Types/SecureLevel.cs @@ -0,0 +1,23 @@ +namespace Signit.Sdk.Types +{ + /** + * 要求的安全级别类型的枚举 + * + * @since 2.0.0 + */ + public enum SecureLevel + { + /** + * 一次性证书. + * + * @since 2.0.0 + */ + DISPOSABLE_CERT, + /** + * 标准证书. + * + * @since 2.0.0 + */ + STANDARD_CERT + } +} \ No newline at end of file diff --git a/Sdk/Types/TokenType.cs b/Sdk/Types/TokenType.cs new file mode 100644 index 0000000000000000000000000000000000000000..06960ee25cfa159f3ba1f7ca251f09622a186c44 --- /dev/null +++ b/Sdk/Types/TokenType.cs @@ -0,0 +1,11 @@ +namespace Signit.Sdk.Types +{ + public enum TokenType + { + CLIENT_CREDENTIALS, + AUTHORIZATION_CODE, + IMPLICIT, + PASSWORD, + REFRESH_TOKEN, + } +} \ No newline at end of file diff --git a/Sdk/Util/AesTool.cs b/Sdk/Util/AesTool.cs new file mode 100644 index 0000000000000000000000000000000000000000..cb3a227d618ad5be04fbfeaa667e314511f4e423 --- /dev/null +++ b/Sdk/Util/AesTool.cs @@ -0,0 +1,98 @@ +using System.Security.Cryptography; + +namespace Signit.Sdk.Util +{ + public static class AesTool + { + /// + /// 获取所使用 AES 算法的块大小,单位:位 + /// + public static int BlockSize { get { return Aes.BlockSize; } } + + /// + /// 获取所使用 AES 算法的 Key 大小,单位:位 + /// + public static int KeySize { get { return Aes.KeySize; } } + + /// + /// 直接通过密码对数据加密。加密所使用的初始化向量根据密码生成。 + /// + /// 需要加密的数据 + /// 密码,不能为 null,但可以是 string.Empty + /// 加密后的数据 + public static byte[] AesEncrypt(this byte[] me, string password) + { + return SymmetricTool.Encrypt(Aes, password, me); + } + + /// + /// 直接通过密码对数据加密。加密所使用的初始化向量根据密码生成。 + /// + /// 需要加密的数据,不能为 null + /// 起始位置 + /// 需要加密的数据长度 + /// 密码,不能为 null,但可以是 string.Empty + /// 加密后的数据 + public static byte[] AesEncrypt(this byte[] me, int start, int length, string password) + { + return SymmetricTool.Encrypt(Aes, password, me, start, length); + } + + /// + /// 直接通过密码对数据解密。解密所使用的初始化向量根据密码生成。 + /// + /// 需要解密的数据 + /// 密码,不能为 null,但可以是 string.Empty + /// 解密后的数据 + public static byte[] AesDecrypt(this byte[] me, string password) + { + return SymmetricTool.Decrypt(Aes, password, me); + } + + /// + /// 使用AES加密数据。 + /// + /// 数据字节 + /// 初始化向量 + /// 密钥 + /// 加密后的数据。 + /// 如果原数据是null,则返回null + /// 如果需要加密字符串,可以使用string.ToByteArray扩展方法 + /// 先把string转换成字符数组 + public static byte[] AesEncrypt(this byte[] me, byte[] iv, byte[] key) + { + return InternalTool.Encrypt(Aes, me, iv, key); + } + + /// + /// 使用AES加密数据 + /// + /// 数据字节 + /// 随机产生初始化向量并通过iv返回。 + /// 如果源数据是null,返回null + /// 密钥 + /// 加密后的数据。如果原数据是null,则返回null + /// 如果需要加密字符串,可以使用string.ToByteArray扩展方法 + /// 先把string转换成字符数组 + public static byte[] AesEncrypt(this byte[] me, out byte[] iv, byte[] key) + { + return InternalTool.Encrypt(Aes, me, out iv, key); + } + + /// + /// 使用AES解密(加密后的)数据 + /// + /// 数据字节 + /// 初始化向量 + /// 密钥 + /// 解密后的数据,即源数据。 + /// 如果数据字节是null,返回null + public static byte[] AesDecrypt(this byte[] me, byte[] iv, byte[] key) + { + return InternalTool.Decrypt(Aes, me, iv, key); + } + + private static Aes Aes { get { return aesInstance ?? (aesInstance = Aes.Create()); } } + private static Aes aesInstance; + } +} diff --git a/Sdk/Util/HmacSignatureBuilder.cs b/Sdk/Util/HmacSignatureBuilder.cs new file mode 100644 index 0000000000000000000000000000000000000000..675674bd9b3917d4ce8071dd4531f10defe9256e --- /dev/null +++ b/Sdk/Util/HmacSignatureBuilder.cs @@ -0,0 +1,368 @@ +using Signit.Sdk.Extentions; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using Viyi.Util; +using Viyi.Util.Codec; +using Viyi.Util.Linq; + +namespace Signit.Sdk.Util +{ + public class HmacSignatureBuilder + { + public enum BuilderMode + { + /// + /// 所有字段均加入运算 + /// + FULL, + + /// + /// 只有请求头字段加入运算 + /// + ONLY_HEADER + } + + public const string DEFAULT_ALGORITHM = "HmacSHA512"; + public const string DEFAULT_CHARSET = "UTF-8"; + public static readonly byte[] DEFAULT_DELIMITER = "\n".ToByteArray(DEFAULT_CHARSET); + private byte[] delimiter = DEFAULT_DELIMITER; + private string charset = DEFAULT_CHARSET; + private string algorithm = DEFAULT_ALGORITHM; + private string scheme; + private string host; + private string method; + private string resource; + private string nonce; + private string apiKey; + private byte[] apiSecret; + private byte[] payload; + private string date; + private string contentType; + + /** + * 设置加入运算的相关字符的字符集编码规则. + * + * @param charset + * 字符集编码规则 + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Charset(String charset) + { + this.charset = charset; + return this; + } + + /** + * 设置加入运算的字符间的分隔符. + * + * @param delimiter + * 字符间的分隔符 + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Delimiter(byte[] delimiter) + { + this.delimiter = delimiter; + return this; + } + + public HmacSignatureBuilder Delimiter(byte delimiter) + { + this.delimiter = new[] { delimiter }; + return this; + } + + /** + * 设置HMAC的算法. + * + * @param algorithm + * HMAC的算法 + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Algorithm(String algorithm) + { + this.algorithm = algorithm; + return this; + } + + /** + * 设置需要加入运算的协议名称. + * + * @param scheme + * 协议名称(如: http/https/ftp/...) + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Scheme(String scheme) + { + this.scheme = scheme; + return this; + } + + /** + * 设置需要加入运算的主机名称. + * + * @param host + * 主机名称(如: signit.cn/10.10.10.10/...) + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Host(String host) + { + this.host = host; + return this; + } + + /** + * 设置需要加入运算的服务器授权给客户端的唯一公开标识.(与apiSecret配套,允许公开). + * + * @param apiKey + * 服务器授权给客户端的唯一公开标识.(与apiSecret配套,允许公开) + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder ApiKey(String apiKey) + { + this.apiKey = apiKey; + return this; + } + + /** + * 设置需要加入运算的请求方法. + * + * @param method + * 请求方法(如: GET/POST/PUT/DELETE/...) + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Method(String method) + { + this.method = method; + return this; + } + + /** + * 设置需要加入运算的请求资源地址(URI). + * + * @param resource + * 请求资源地址(URI)(如: '/v1/users','/v1/users/123',...) + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Resource(String resource) + { + this.resource = resource; + return this; + } + + /** + * 设置需要加入运算的请求内容类型(Content-Type). + * + * @param contentType + * 请求内容类型(Content-Type)(如: 'application/json','image/jpeg',...) + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder ContentType(String contentType) + { + this.contentType = contentType; + return this; + } + + /** + * 设置需要加入运算的请求时间(Date). + * + * @param date + * 请求时间(Date)(如: 'Sat, 02 Jul 2011 20:45:57 GMT','Wed, 02 Nov + * 2016 03:25:54 GMT',...) + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Date(String date) + { + this.date = date; + return this; + } + + /** + * 设置需要加入运算的随机数. + * + * @param nonce + * 随机数(如: '6m0S4nyH1dg7K2gh','8NQq53cLR4g5Y52I',...) + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Nonce(String nonce) + { + this.nonce = nonce; + return this; + } + + /** + * 设置需要加入运算的服务器授权给客户端的私密密钥.(与apiKey配套,但不公开). + * + + * @param apiSecret + + * 服务器授权给客户端的私密密钥.(与apiKey配套,但不公开) + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder ApiSecret(byte[] apiSecret) + { + this.apiSecret = apiSecret; + return this; + } + + /** + * 设置需要加入运算的请求数据. + * + + * @param payload + + * 请求数据 + * @return 当前HMAC建造器对象 + * @author zhd + * @since 1.0.0 + */ + public HmacSignatureBuilder Payload(byte[] payload) + { + this.payload = payload; + return this; + } + + public string BuildAsHex(BuilderMode builderMode = BuilderMode.FULL) + { + return Build(builderMode).HexEncode(); + } + + public string BuildAsBase64(BuilderMode builderMode = BuilderMode.FULL) + { + return Build(builderMode).Base64Encode(); + } + + /** + * 完成HMAC认证消息的构建,并获得签名摘要值. + * + * @param builderMode + * 构建模式的枚举 + * @return HMAC原始签名摘要值的内存数据字节.若构建失败,则返回null + * @author zhd + * @since 1.0.0 + */ + public byte[] Build(BuilderMode builderMode = BuilderMode.FULL) + { + Func builder = null; + switch (builderMode) + { + case BuilderMode.FULL: + builder = GetFullStream; + break; + case BuilderMode.ONLY_HEADER: + builder = GetOnlyHeaderStream; + break; + } + + if (builder == null) + { + Debug.WriteLine($"不支持的参数类型: {builderMode}"); + return null; + } + + var hmac = HMAC.Create(algorithm); + hmac.Key = apiSecret; + return hmac.ComputeHash(builder()); + } + + + // 完整的数据运算 + private Stream GetFullStream() + { + MemoryStream stream = new MemoryStream(); + SignitStreamWriter writer = new SignitStreamWriter(stream) + .SetDelimiter(delimiter) + .SetEncoding(charset); + + new[] { apiKey, contentType, date, host, method, nonce } + .ForEach(writer.Write); + + if (payload?.Length > 0) + { + writer.Write(payload); + } + + new[] { resource, scheme } + .ForEach(writer.Write); + + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + + private Stream GetOnlyHeaderStream() + { + MemoryStream stream = new MemoryStream(); + SignitStreamWriter writer = new SignitStreamWriter(stream) + .SetDelimiter(delimiter) + .SetEncoding(charset); + + new[] { apiKey, contentType, date, host, method, nonce, resource, scheme } + .ForEach(writer.Write); + + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + + public void VerifySignature(byte[] expect, + BuilderMode mode = BuilderMode.FULL, + string errMessage = null) + { + if (Enumerable.SequenceEqual(expect, Build(mode))) + { + return; + } + + throw new ArgumentException(errMessage); + } + + public void VerifyHexSignature(string expect, + BuilderMode mode = BuilderMode.FULL, + string errMessage = null) + { + if (expect == BuildAsHex(mode)) + { + return; + } + + throw new ArgumentException(errMessage); + } + + public void VerifyBase64Signature(string expect, + BuilderMode mode = BuilderMode.FULL, + string errMessage = null) + { + if (expect == BuildAsBase64(mode)) + { + return; + } + + throw new ArgumentException(errMessage); + } + } +} diff --git a/Sdk/Util/InternalTool.cs b/Sdk/Util/InternalTool.cs new file mode 100644 index 0000000000000000000000000000000000000000..718c57df4279dda08240bedf43d658e64355ac20 --- /dev/null +++ b/Sdk/Util/InternalTool.cs @@ -0,0 +1,123 @@ +using System.Security.Cryptography; +using Viyi.Util.Codec; + +namespace Signit.Sdk.Util +{ + internal static class InternalTool + { + #region 对称加密算法模板 + public static byte[] Encrypt(SymmetricAlgorithm algorithm, byte[] data, byte[] iv, byte[] key) + { + if (data == null) { return null; } + + key = CheckAndPadKey(key, algorithm); + ICryptoTransform transform = algorithm.CreateEncryptor(key, iv); + return transform.TransformFinalBlock(data, 0, data.Length); + } + + public static byte[] Encrypt(SymmetricAlgorithm algorithm, byte[] data, out byte[] iv, byte[] key) + { + if (data == null) + { + iv = null; + return null; + } + + key = CheckAndPadKey(key, algorithm); + iv = algorithm.IV; + ICryptoTransform transform = algorithm.CreateEncryptor(key, iv); + return transform.TransformFinalBlock(data, 0, data.Length); + } + + internal static byte[] Encrypt( + SymmetricAlgorithm algorithm, + byte[] data, int start, int length, + byte[] iv, byte[] key) + { + if (data == null) { return null; } + key = CheckAndPadKey(key, algorithm); + ICryptoTransform transform = algorithm.CreateEncryptor(key, iv); + return transform.TransformFinalBlock(data, start, length); + } + + public static byte[] Decrypt(SymmetricAlgorithm algorithm, byte[] data, byte[] iv, byte[] key) + { + if (data == null) { return null; } + + key = CheckAndPadKey(key, algorithm); + ICryptoTransform transform = algorithm.CreateDecryptor(key, iv); + return transform.TransformFinalBlock(data, 0, data.Length); + } + #endregion + + #region 根据加密算法要求补齐Key + + private static readonly byte[] PadingSalt + = "4KC8A0HCSLFDCPDREVQUVYYCPHB9L549UQ0WITFITWV19D8751QEXN6FTPIT8EGW".HexDecode(); + + private const int PaddingIterations = 64; + + /// + /// 使用PBKDF2算法补齐密码 + /// + /// + /// + /// + internal static byte[] PadKey(byte[] key, int length) + { + return key == null + ? null + : new Rfc2898DeriveBytes(key, PadingSalt, PaddingIterations).GetBytes(length); + } + + /// + /// 检查key的长度对算法来说是否合法,如果合法,直接返回。 + /// 如果不合法,使用PBKDF2算法对其进行补齐至最接近的长度,然后返回 + /// + /// + /// + /// + private static byte[] CheckAndPadKey(byte[] key, SymmetricAlgorithm algorithm) + { + if (key == null) { return null; } + + int length = key.Length * 8; + foreach (KeySizes sizes in algorithm.LegalKeySizes) + { + // 不在范围内,key无效 + if (length < sizes.MinSize || length > sizes.MaxSize) + { + continue; + } + + // 在范围内,又没有步长限制,key有效 + if (sizes.SkipSize == 0) { return key; } + + // 检查如果key的长度是合法长度,不需要Padding,直接返回 + if ((length - sizes.MinSize) % sizes.SkipSize == 0) + { + return key; + } + } + + // 检查key的长度不属于任何合法长度 + KeySizes firstSizes = algorithm.LegalKeySizes[0]; + int paddingLength; + if (length < firstSizes.MinSize) + { + paddingLength = firstSizes.MinSize; + } + else if (length > firstSizes.MaxSize) + { + paddingLength = firstSizes.MaxSize; + } + else + { + paddingLength = length + (length - firstSizes.MinSize) % firstSizes.SkipSize; + } + + return PadKey(key, paddingLength / 8); + } + #endregion + } +} diff --git a/Sdk/Util/SignitStreamWriter.cs b/Sdk/Util/SignitStreamWriter.cs new file mode 100644 index 0000000000000000000000000000000000000000..80f9632a0f4add21445568fafe5e32aaa8feb59e --- /dev/null +++ b/Sdk/Util/SignitStreamWriter.cs @@ -0,0 +1,57 @@ +using Signit.Sdk.Extentions; +using System.IO; +using System.Text; +using Viyi.Util; + +namespace Signit.Sdk.Util +{ + class SignitStreamWriter + { + public readonly Stream Stream; + private byte[] delimiter = new byte[0]; + private Encoding encoding = Encoding.UTF8; + + public SignitStreamWriter(Stream stream) + { + Stream = stream; + } + + public SignitStreamWriter SetDelimiter(byte[] delimiter) + { + this.delimiter = delimiter ?? new byte[0]; + return this; + } + + public SignitStreamWriter SetEncoding(string encoding) + { + this.encoding = Encoding.GetEncoding(encoding); + return this; + } + + public SignitStreamWriter SetEncoding(Encoding encoding) + { + this.encoding = encoding; + return this; + } + + public SignitStreamWriter Write(byte[] data) + { + Stream.Write(data); + Stream.Write(delimiter); + return this; + } + + public SignitStreamWriter Write(string s) + { + process(); + return this; + + void process() + { + if (s == null) { return; } + Stream.Write(s.ToByteArray(encoding)) + .Write(delimiter); + } + } + } +} diff --git a/Sdk/Util/SymmetricTool.cs b/Sdk/Util/SymmetricTool.cs new file mode 100644 index 0000000000000000000000000000000000000000..06c6b9eaa04005020c2603db5cbee7b46e813246 --- /dev/null +++ b/Sdk/Util/SymmetricTool.cs @@ -0,0 +1,121 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Signit.Sdk.Util +{ + /// + ///

用于对称加密算法的简化使用扩展方法工具类。该工具类使用户只需要提供密码即可对数据进行加解密。 + /// 加解密所使用的初始化向量是通过密码来计算的。

+ ///

由于该工具类使用了特定的生成初始化向量的算法,所以 SymmetricTool.Decrypt 方法只能解密

+ /// 由 SymmetricTool.Encrypt 生成的,或通过其兼容算法生成的密文。 + ///
+ /// + /// string EncryptConnectionString(string connStr, string password) { + /// Aes aes = Aes.Create(); + /// byte[] ed = aes.Encrypt(password, connStr.ToByteArray(Encoding.UTF8)); + /// return ed.HexEncode(); + /// } + /// + /// string DecryptConnectionString(string hexStr, string password) { + /// byte[] d = Aes.Create().Decrypt(password, hexStr.HexDecode()); + /// return d.GetString(Encoding.UTF8); + /// } + /// + /// + /// 对于 AES、DES 和 3DES 算法,可以直接使用 AesToolDesTool 中定义的相关扩展方法。 + /// + /// + /// + public static class SymmetricTool + { + /// + /// 根据指定的对称加密算法,使用给定的密码对数据进行加密。 + /// + /// 对称加密算法 + /// 密码,不能为 null,可以是 string.Empty + /// 需要加密的数据,数据为 null,则加密结果为 null + /// 已加密的数据 + /// passwordnull + /// + /// 密码原文会通过 UTF-8 编码方式转换成字符数据用于加密。 + /// + public static byte[] Encrypt(this SymmetricAlgorithm me, string password, byte[] data) + { + if (password == null) + { + throw new ArgumentNullException("password"); + } + + if (data == null) + { + return null; + } + + byte[] keyData = Encoding.UTF8.GetBytes(password); + byte[] key = InternalTool.PadKey(keyData, me.KeySize / 8); + byte[] iv = InternalTool.PadKey(keyData, me.BlockSize / 8); + return InternalTool.Encrypt(me, data, iv, key); + } + + /// + /// 根据指定的对称加密算法,使用给定的密码对数据的一部分或全部进行加密。 + /// + /// 对称加密算法 + /// 密码,不能为 null,可以是 string.Empty + /// 需要加密的数据,数据不能为 null + /// 原数据起始位置 + /// 需要加密的原数据长度 + /// 已加密的数据 + /// passworddatanull + /// + /// 密码原文会通过 UTF-8 编码方式转换成字符数据用于加密。 + /// + public static byte[] Encrypt(this SymmetricAlgorithm me, string password, byte[] data, int start, int length) + { + if (password == null) + { + throw new ArgumentNullException("password"); + } + + if (data == null) + { + throw new ArgumentNullException("data"); + } + + byte[] keyData = Encoding.UTF8.GetBytes(password); + byte[] key = InternalTool.PadKey(keyData, me.KeySize / 8); + byte[] iv = InternalTool.PadKey(keyData, me.BlockSize / 8); + return InternalTool.Encrypt(me, data, start, length, iv, key); + } + + /// + /// 根据指定的对称加密算法,使用给定的密码对加密数据进行解密。 + /// + /// 对称加密算法 + /// 密码,不能为 null,可以是 string.Empty + /// 已加密的数据 + /// 解密后的数据 + /// passwordnull + /// + /// 密码原文会通过 UTF-8 编码方式转换成字符数据用于解密。 + /// + public static byte[] Decrypt(this SymmetricAlgorithm me, string password, byte[] encryptedData) + { + if (password == null) + { + throw new ArgumentNullException("password"); + } + + if (encryptedData == null) + { + return null; + } + + byte[] keyData = Encoding.UTF8.GetBytes(password); + byte[] key = InternalTool.PadKey(keyData, me.KeySize / 8); + byte[] iv = InternalTool.PadKey(keyData, me.BlockSize / 8); + return InternalTool.Decrypt(me, encryptedData, iv, key); + } + } +} diff --git a/Sdk/app.config b/Sdk/app.config new file mode 100644 index 0000000000000000000000000000000000000000..6d05181ceb303c22a9940403cd81f260c4399494 --- /dev/null +++ b/Sdk/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Sdk/packages.config b/Sdk/packages.config new file mode 100644 index 0000000000000000000000000000000000000000..b809fefc553d2e2ff4f04cb810f4db349b7a9108 --- /dev/null +++ b/Sdk/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file