chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.Json;
using System.Xml.Serialization;
 
namespace OpenTap.Authentication
{
    /// <summary> 
    /// Represents a set of Oauth2/OpenID Connect tokens (access and possibly refresh token) that grants access to a given domain.
    /// </summary>
    public class TokenInfo
    {
        /// <summary>
        /// An enumeration of known token kinds
        /// </summary>
        private enum TokenKind
        {
            Jwt,
            UserToken
        }
 
        /// <summary>
        /// The kind of token this instance contains
        /// </summary>
        private TokenKind Kind => DetermineTokenKind();
 
        private TokenKind DetermineTokenKind()
        {
            // Repository user tokens are guaranteed to never contain a period.
            return AccessToken.Contains(".") ? TokenKind.Jwt : TokenKind.UserToken;
        }
        
        static DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
 
        /// <summary>
        /// Raw access token string. This value can be used as a Bearer token. The HttpClient 
        /// returned from <see cref="AuthenticationSettings.GetClient"/> will automatically do 
        /// this for requests that go to domains that match <see cref="Domain"/>.
        /// </summary>
        public string AccessToken { get; set; }
 
        /// <summary>
        /// Raw refresh token string. May be null if no refresh token is available.
        /// </summary>
        public string RefreshToken { get; set; }
 
        private string domain;
        /// <summary> 
        /// The hostname or IP address this token is intended for. Used by the HttpClient 
        /// returned from <see cref="AuthenticationSettings.GetClient"/> to determine which TokenInfo
        /// in the <see cref="AuthenticationSettings.Tokens"/> list to use for a given request.
        /// </summary>
        public string Domain 
        { 
            get => domain; 
            set
            {
                if (Uri.IsWellFormedUriString(value, UriKind.Absolute))
                    throw new ArgumentException("Domain should only be the host part of a URI and not a full absolute URI.");
                domain = value;
            }
        }
 
        private Dictionary<string, string> _Claims;
        /// <summary>
        /// Claims contained in the <see cref="AccessToken"/>.
        /// </summary>
        public IReadOnlyDictionary<string, string> Claims
        {
            get
            {
                if (_Claims == null)
                    _Claims = GetClaims();
                return _Claims;
            }
        }
 
        private Dictionary<string, string> GetClaims()
        {
            if (Kind == TokenKind.Jwt)
                return GetPayload().RootElement.EnumerateObject().ToDictionary(c => c.Name, c => c.Value.ToString());
            return new Dictionary<string, string>();
        }
 
        /// <summary> Expiration date of the <see cref="AccessToken"/>. </summary>
        [XmlIgnore]
        [Obsolete("Expiration time is not supported. Future token types may not contain expiration information. " +
                  "Consider manually decoding the token if you know the specific format.")]
        public DateTime Expiration =>
            Kind == TokenKind.Jwt ? unixEpoch.AddSeconds(long.Parse(Claims["exp"])) : DateTime.MaxValue;
 
        JsonDocument payload;
 
        JsonDocument GetPayload()
        {
            if (payload != null) return payload;
            var payloadData = AccessToken.Split('.')[1];
            payload = JsonDocument.Parse(Base64UrlDecode(payloadData));
            return payload;
        }
 
 
        byte[] Base64UrlDecode(string encoded)
        {
            string substituded = encoded;
            substituded = substituded.Replace('-', '+');
            substituded = substituded.Replace('_', '/');
            while (substituded.Length % 4 != 0)
            {
                substituded += '=';
            }
            return Convert.FromBase64String(substituded);
        }
 
        /// <summary>
        /// Constructor used by serializer, please use constructor with arguments from user code.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public TokenInfo()
        {
 
        }
 
        /// <summary>
        /// Default constructor from user code
        /// </summary>
        /// <param name="access_token">The raw token string for the access token</param>
        /// <param name="refresh_token">Access, Refresh or ID type</param>
        /// <param name="domain">Domain name for which this token is valid</param>
        public TokenInfo(string access_token, string refresh_token, string domain)
        {
            AccessToken = access_token;
            RefreshToken = refresh_token;
            Domain = domain;
        }
 
        /// <summary> Creates a TokenInfo object based on the given OAuth response (json format). </summary>
        public static TokenInfo FromResponse(string response, string domain)
        {
            var ti = new TokenInfo();
            ti.Domain = domain;
            var json = JsonDocument.Parse(response);
 
            if (json.RootElement.TryGetProperty("access_token", out var accessTokenData))
                ti.AccessToken = accessTokenData.GetString();
 
            if (json.RootElement.TryGetProperty("refresh_token", out var refreshTokenData))
                ti.RefreshToken = refreshTokenData.GetString();
            return ti;
        }
    }
}