omniauth-wecom 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE +21 -0
- data/README.md +77 -0
- data/lib/omniauth/strategies/wecom.rb +250 -0
- data/lib/omniauth/wecom/version.rb +7 -0
- data/lib/omniauth/wecom.rb +4 -0
- data/lib/omniauth-wecom.rb +3 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 20123a046b4db04e1a3afcf76aa4bf7593114df4bc7c67e06dfef9506418658d
|
|
4
|
+
data.tar.gz: ccba1c2ed792ec30ec7349d5aa9523b8c9795ca6a552b89d3def12b4ad1bbfa9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: dbaade7da462cbba6f25234be688fde4712b469af3867819a932040529f39d0b5324a56d9e8b5e56178e48f256714e9da50ddccce4d8563b14b278811834dfb1
|
|
7
|
+
data.tar.gz: a6790a76cc51c440bdc1c606582530cdf58966ef5f3730de1b3e2d1e6f5d7c09c83dfb615f92838c70c198e6464f20f5fc69684cbbc99b5b9acaf310b3e5ce0c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## 1.0.0 (2026-09-08)
|
|
6
|
+
|
|
7
|
+
- Extract the WeCom CorpApp OmniAuth strategy from the JiHu GitLab monolith into a standalone gem.
|
|
8
|
+
- Add standalone tests, linting, gem packaging, and contributor documentation.
|
|
9
|
+
|
|
10
|
+
The extraction is tracked in [work item 5299](https://jihulab.com/gitlab-cn/gitlab/-/work_items/5299) and is based on [MR 3776](https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/3776).
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JiHu (Shanghai) Information Technology Co., Ltd.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# omniauth-wecom
|
|
2
|
+
|
|
3
|
+
`omniauth-wecom` is an [OmniAuth](https://github.com/omniauth/omniauth) strategy for signing in through a WeCom (WeChat Work) custom, corp-internal application. It supports the desktop CorpApp QR-code flow. Third-party service-provider applications and the WeCom in-app WebView flow are outside its scope.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add the gem to your `Gemfile`:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'omniauth-wecom', '~> 1.0.0'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then run `bundle install`.
|
|
14
|
+
|
|
15
|
+
The gem supports Ruby 3.2 or later and OmniAuth 2.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
use OmniAuth::Builder do
|
|
21
|
+
provider :wecom,
|
|
22
|
+
ENV.fetch('WECOM_CORP_ID'),
|
|
23
|
+
ENV.fetch('WECOM_CORP_SECRET'),
|
|
24
|
+
agent_id: ENV.fetch('WECOM_AGENT_ID')
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Option | Required | Description |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `client_id` (first argument) | Yes | WeCom CorpID |
|
|
31
|
+
| `client_secret` (second argument) | Yes | Secret of the custom application |
|
|
32
|
+
| `agent_id` | Yes | AgentID of the custom application |
|
|
33
|
+
| `fetch_user_detail` | No | Fetch the member's name and email from the address book; defaults to `false` and requires the relevant permission |
|
|
34
|
+
| `authorize_url` | No | Override the QR-code authorization endpoint for development or a private host |
|
|
35
|
+
| `client_options[:site]` | No | Override the API host for development or a private host |
|
|
36
|
+
|
|
37
|
+
The host application must follow OmniAuth 2's integration requirements: start authentication with `POST`, preserve the session from the request phase through the callback, and keep request validation and CSRF protection enabled. The strategy stores an OmniAuth `state` value in the session and relies on `omniauth-oauth2` to reject a missing or mismatched value at callback time.
|
|
38
|
+
|
|
39
|
+
Host overrides are available for private endpoints and local stubs. Their presence does not imply official support for any particular private WeCom deployment.
|
|
40
|
+
|
|
41
|
+
## Authentication flow
|
|
42
|
+
|
|
43
|
+
The access token belongs to the application, not the member. The strategy:
|
|
44
|
+
|
|
45
|
+
1. Redirects the browser to WeCom's `CorpApp` QR-code endpoint.
|
|
46
|
+
2. Exchanges the CorpID and application secret for a corp-level access token.
|
|
47
|
+
3. Uses that token and the callback code to resolve the member's UserId.
|
|
48
|
+
4. Optionally reads the member's name and email when `fetch_user_detail` is enabled.
|
|
49
|
+
|
|
50
|
+
Only corp members can sign in. Responses without a `userid`, including external contacts, are rejected. If WeCom rejects an access token, the strategy invalidates it and retries the identity lookup once. When the optional address-book lookup returns a WeCom API error, such as missing permission or a rejected token, the strategy falls back to the UserId instead of failing sign-in. Other network or HTTP failures are not covered by this fallback.
|
|
51
|
+
|
|
52
|
+
## Identity and auth hash
|
|
53
|
+
|
|
54
|
+
The auth hash uses `"<CorpID>:<UserId>"` as `uid`, because a UserId is unique only within one corp. `info` always includes the UserId as `nickname`; it uses the UserId as `name` unless member details are available.
|
|
55
|
+
|
|
56
|
+
The corp-level token can grant broad application access, and the member response can contain a long-lived ticket. To keep both out of sessions and logs:
|
|
57
|
+
|
|
58
|
+
- `credentials` is empty.
|
|
59
|
+
- `extra` contains only `corp_id`.
|
|
60
|
+
|
|
61
|
+
## Extension hooks and token storage
|
|
62
|
+
|
|
63
|
+
The strategy exposes five protected hooks for host applications:
|
|
64
|
+
|
|
65
|
+
- `parse_json(raw)` parses WeCom response bodies.
|
|
66
|
+
- `request_user_info` performs the member identity request.
|
|
67
|
+
- `request_user_detail` performs the optional address-book request.
|
|
68
|
+
- `corp_access_token` obtains the application token and returns `[token, expires_in]`.
|
|
69
|
+
- `invalidate_access_token` invalidates token storage after WeCom rejects a token.
|
|
70
|
+
|
|
71
|
+
The standalone defaults fetch a token directly from WeCom and do not provide shared token caching. GitLab-specific HTTP adapters, hardened JSON parsing, and `Gitlab::Wecom::AccessToken` caching remain in the GitLab monolith and are not part of this gem.
|
|
72
|
+
|
|
73
|
+
## Development
|
|
74
|
+
|
|
75
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). This project is licensed under the MIT License; see [LICENSE](LICENSE).
|
|
76
|
+
|
|
77
|
+
The extraction was tracked in [work item 5299](https://jihulab.com/gitlab-cn/gitlab/-/work_items/5299), following the implementation merged in [MR 3776](https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/3776).
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'omniauth-oauth2'
|
|
6
|
+
|
|
7
|
+
module OmniAuth
|
|
8
|
+
module Strategies
|
|
9
|
+
# Sign-in with a WeCom (WeChat Work) custom, corp-internal application.
|
|
10
|
+
#
|
|
11
|
+
# This is not plain OAuth 2.0. The access token is issued to the *app*, not
|
|
12
|
+
# to the user: it comes from corpid plus corpsecret and has nothing to do
|
|
13
|
+
# with the authorization code. The code is presented at the last step,
|
|
14
|
+
# alongside that token, purely to resolve who signed in. That is why
|
|
15
|
+
# `build_access_token` is replaced wholesale.
|
|
16
|
+
#
|
|
17
|
+
# Only corp members can sign in. External contacts and WeChat users outside
|
|
18
|
+
# the corp come back without a userid and are rejected.
|
|
19
|
+
class Wecom < OmniAuth::Strategies::OAuth2
|
|
20
|
+
# Scan-the-QR-code entry point, for a normal desktop browser. Authorizing
|
|
21
|
+
# inside the WeCom client's own WebView uses a different endpoint and is
|
|
22
|
+
# not supported yet.
|
|
23
|
+
AUTHORIZE_URL = 'https://login.work.weixin.qq.com/wwlogin/sso/login'
|
|
24
|
+
|
|
25
|
+
API_SITE = 'https://qyapi.weixin.qq.com'
|
|
26
|
+
TOKEN_PATH = '/cgi-bin/gettoken'
|
|
27
|
+
USER_INFO_PATH = '/cgi-bin/auth/getuserinfo'
|
|
28
|
+
USER_DETAIL_PATH = '/cgi-bin/user/get'
|
|
29
|
+
|
|
30
|
+
# How extern_uid is encoded: CorpID and UserId joined by a colon.
|
|
31
|
+
#
|
|
32
|
+
# A UserId is only unique inside one corp, so it means nothing on its own.
|
|
33
|
+
# Carrying the CorpID means that after the instance switches corps, old
|
|
34
|
+
# bindings stop matching instead of quietly resolving to whoever happens
|
|
35
|
+
# to share that UserId in the new corp.
|
|
36
|
+
#
|
|
37
|
+
# WeCom UserIds allow letters, digits and `-_@.`, so a colon separates
|
|
38
|
+
# them unambiguously.
|
|
39
|
+
UID_SEPARATOR = ':'
|
|
40
|
+
|
|
41
|
+
# Raised when the provider is wired up without the settings CorpApp
|
|
42
|
+
# sign-in needs. Surfaced at request time rather than at boot so that one
|
|
43
|
+
# bad provider entry cannot stop the whole application from starting.
|
|
44
|
+
ConfigurationError = Class.new(StandardError)
|
|
45
|
+
|
|
46
|
+
# Raised when WeCom refuses the corp token, so it can be refreshed once.
|
|
47
|
+
TokenRejected = Class.new(StandardError)
|
|
48
|
+
|
|
49
|
+
option :name, 'wecom'
|
|
50
|
+
|
|
51
|
+
# AgentID of the custom app. Required: CorpApp scan-code sign-in is
|
|
52
|
+
# scoped to a single app and WeCom rejects the request without it.
|
|
53
|
+
option :agent_id, nil
|
|
54
|
+
|
|
55
|
+
# Reading a member's name and email needs the address book permission,
|
|
56
|
+
# which many customers will not grant, so it is off by default. Without
|
|
57
|
+
# it `info` carries only the userid and GitLab generates a placeholder
|
|
58
|
+
# email.
|
|
59
|
+
option :fetch_user_detail, false
|
|
60
|
+
|
|
61
|
+
# Overridable so the strategy can point at a WeCom private deployment,
|
|
62
|
+
# or at a local stub while developing.
|
|
63
|
+
option :authorize_url, AUTHORIZE_URL
|
|
64
|
+
|
|
65
|
+
# A rejected or expired token comes back as one of these; the caller
|
|
66
|
+
# refreshes once rather than failing the sign-in.
|
|
67
|
+
TOKEN_REJECTED_CODES = [40014, 41001, 42001].freeze
|
|
68
|
+
|
|
69
|
+
option :client_options, {
|
|
70
|
+
site: API_SITE,
|
|
71
|
+
connection_opts: { request: { timeout: 20, open_timeout: 10 } }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
uid { "#{corp_id}#{UID_SEPARATOR}#{raw_user_info['userid']}" }
|
|
75
|
+
|
|
76
|
+
info do
|
|
77
|
+
{
|
|
78
|
+
name: user_detail['name'] || raw_user_info['userid'],
|
|
79
|
+
nickname: raw_user_info['userid'],
|
|
80
|
+
email: user_detail['email']
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# `credentials` and `extra` are overridden as methods rather than as DSL
|
|
85
|
+
# blocks: OmniAuth merges the whole ancestor stack, so an empty block here
|
|
86
|
+
# would still let the OAuth2 base contribute its own keys.
|
|
87
|
+
|
|
88
|
+
# The token is corp-level: it can send messages and read the address book
|
|
89
|
+
# for the whole company. Sign-in needs none of it, so keep it out of the
|
|
90
|
+
# auth hash, which is stored in the session and shows up in logs.
|
|
91
|
+
def credentials
|
|
92
|
+
{}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Deliberately not the raw payload: it also returns a member ticket, a
|
|
96
|
+
# credential that stays valid for weeks. Only the corp id is needed here.
|
|
97
|
+
def extra
|
|
98
|
+
{ corp_id: corp_id }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def request_phase
|
|
102
|
+
if options.agent_id.nil? || options.agent_id.to_s.empty?
|
|
103
|
+
raise ConfigurationError, 'WeCom provider requires `agent_id` to be configured'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
redirect authorize_url
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# WeCom matches the trusted domain against the URL without its query
|
|
110
|
+
# string, so the callback must not carry one.
|
|
111
|
+
def callback_url
|
|
112
|
+
full_host + callback_path
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def authorize_url
|
|
116
|
+
# Take the state from authorize_params: it also writes the value into
|
|
117
|
+
# the session, which is what callback_phase compares against. Rolling
|
|
118
|
+
# our own state here would silently disable the CSRF check.
|
|
119
|
+
state = authorize_params[:state]
|
|
120
|
+
|
|
121
|
+
query = {
|
|
122
|
+
login_type: 'CorpApp',
|
|
123
|
+
appid: corp_id,
|
|
124
|
+
agentid: options.agent_id,
|
|
125
|
+
redirect_uri: callback_url,
|
|
126
|
+
state: state
|
|
127
|
+
}.reject { |_, value| value.nil? || value.to_s.empty? }
|
|
128
|
+
|
|
129
|
+
"#{options.authorize_url}?#{URI.encode_www_form(query)}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
protected
|
|
133
|
+
|
|
134
|
+
def build_access_token
|
|
135
|
+
token, expires_in = corp_access_token
|
|
136
|
+
|
|
137
|
+
::OAuth2::AccessToken.new(
|
|
138
|
+
client, token,
|
|
139
|
+
expires_in: expires_in,
|
|
140
|
+
# WeCom reads the token from the query string; it has no Bearer support
|
|
141
|
+
mode: :query,
|
|
142
|
+
param_name: 'access_token'
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Fetches the corp-level token straight from WeCom.
|
|
147
|
+
#
|
|
148
|
+
# Overridden inside GitLab to go through a shared cache: WeCom hands out
|
|
149
|
+
# one token per app, and asking for a new one can invalidate the token
|
|
150
|
+
# another part of the app is still using.
|
|
151
|
+
#
|
|
152
|
+
# @return [Array(String, Integer)] the token and its lifetime in seconds
|
|
153
|
+
def corp_access_token
|
|
154
|
+
body = parse_response(
|
|
155
|
+
client.request(:get, TOKEN_PATH, params: { corpid: corp_id, corpsecret: client.secret })
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
[body['access_token'], body['expires_in']]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Overridden inside GitLab so the call goes through the instance's
|
|
162
|
+
# controlled HTTP client instead of the gem's own connection.
|
|
163
|
+
def request_user_info
|
|
164
|
+
access_token.get(USER_INFO_PATH, params: { code: request.params['code'] })
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Same reason as `request_user_info`: kept as a hook so GitLab can route
|
|
168
|
+
# it through the controlled HTTP client too.
|
|
169
|
+
def request_user_detail
|
|
170
|
+
access_token.get(USER_DETAIL_PATH, params: { userid: raw_user_info['userid'] })
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Overridden inside GitLab to evict the shared token cache.
|
|
174
|
+
def invalidate_access_token; end
|
|
175
|
+
|
|
176
|
+
# Overridden inside GitLab to use a hardened parser.
|
|
177
|
+
def parse_json(raw)
|
|
178
|
+
::JSON.parse(raw)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def corp_id
|
|
182
|
+
client.id
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def raw_user_info
|
|
188
|
+
@raw_user_info ||= fetch_user_info
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def fetch_user_info
|
|
192
|
+
member_info
|
|
193
|
+
rescue TokenRejected
|
|
194
|
+
# The cached token was retired early. Drop it, get a new one, try once.
|
|
195
|
+
invalidate_access_token
|
|
196
|
+
self.access_token = build_access_token
|
|
197
|
+
|
|
198
|
+
begin
|
|
199
|
+
member_info
|
|
200
|
+
rescue TokenRejected => e
|
|
201
|
+
raise CallbackError.new(:invalid_credentials, e.message)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def member_info
|
|
206
|
+
info = parse_response(request_user_info)
|
|
207
|
+
|
|
208
|
+
if info['userid'].nil? || info['userid'].to_s.empty?
|
|
209
|
+
raise CallbackError.new(:invalid_credentials,
|
|
210
|
+
'WeCom did not return a userid: the account is not a member of this corp')
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
info
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def user_detail
|
|
217
|
+
return {} unless options.fetch_user_detail
|
|
218
|
+
|
|
219
|
+
@user_detail ||= begin
|
|
220
|
+
parse_response(request_user_detail)
|
|
221
|
+
rescue CallbackError, TokenRejected
|
|
222
|
+
# Details are optional. Whether the address book permission is missing
|
|
223
|
+
# or the token was retired between the two calls, fall back to the
|
|
224
|
+
# userid alone rather than failing the sign-in.
|
|
225
|
+
{}
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# WeCom answers 200 even for failures and puts the real status in
|
|
230
|
+
# `errcode`, so the body is what decides success.
|
|
231
|
+
def parse_response(response)
|
|
232
|
+
body = parse_json(response.body)
|
|
233
|
+
errcode = body['errcode']
|
|
234
|
+
|
|
235
|
+
return body if errcode.nil? || errcode.to_i == 0
|
|
236
|
+
|
|
237
|
+
if TOKEN_REJECTED_CODES.include?(errcode.to_i)
|
|
238
|
+
raise TokenRejected, "WeCom rejected the access token: #{errcode} #{body['errmsg']}"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
raise CallbackError.new(:invalid_credentials,
|
|
242
|
+
"WeCom API error #{errcode}: #{body['errmsg']}")
|
|
243
|
+
rescue ::JSON::ParserError
|
|
244
|
+
raise CallbackError.new(:invalid_credentials, 'WeCom returned a malformed response')
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
OmniAuth.config.add_camelization 'wecom', 'Wecom'
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: omniauth-wecom
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- JiHu GitLab
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: omniauth
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: omniauth-oauth2
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.8'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.8'
|
|
40
|
+
description: OmniAuth strategy for signing in with a WeCom custom (internal) application.
|
|
41
|
+
executables: []
|
|
42
|
+
extensions: []
|
|
43
|
+
extra_rdoc_files: []
|
|
44
|
+
files:
|
|
45
|
+
- CHANGELOG.md
|
|
46
|
+
- LICENSE
|
|
47
|
+
- README.md
|
|
48
|
+
- lib/omniauth-wecom.rb
|
|
49
|
+
- lib/omniauth/strategies/wecom.rb
|
|
50
|
+
- lib/omniauth/wecom.rb
|
|
51
|
+
- lib/omniauth/wecom/version.rb
|
|
52
|
+
homepage: https://gitlab.com/gitlab-jh/jh-team/omniauth-wecom
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata:
|
|
56
|
+
source_code_uri: https://gitlab.com/gitlab-jh/jh-team/omniauth-wecom
|
|
57
|
+
bug_tracker_uri: https://gitlab.com/gitlab-jh/jh-team/omniauth-wecom/-/issues
|
|
58
|
+
changelog_uri: https://gitlab.com/gitlab-jh/jh-team/omniauth-wecom/-/blob/main/CHANGELOG.md
|
|
59
|
+
allowed_push_host: https://rubygems.org
|
|
60
|
+
rubygems_mfa_required: 'true'
|
|
61
|
+
rdoc_options: []
|
|
62
|
+
require_paths:
|
|
63
|
+
- lib
|
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.2'
|
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0'
|
|
74
|
+
requirements: []
|
|
75
|
+
rubygems_version: 4.0.13
|
|
76
|
+
specification_version: 4
|
|
77
|
+
summary: WeCom (WeChat Work) strategy for OmniAuth
|
|
78
|
+
test_files: []
|